← flaviocopes.com

How to use the useCallback React hook

Published Jul 18 2019

Psssst! The 2023 WEB DEVELOPMENT BOOTCAMP is starting on FEBRUARY 01, 2023! SIGNUPS ARE NOW OPEN to this 10-weeks cohort course. Learn the fundamentals, HTML, CSS, JS, Tailwind, React, Next.js and much more! ✨

Check out my React hooks introduction first, if you’re new to them.

One React hook I sometimes use is useCallback.

import React, { useCallback } from 'react'

This hook is useful when you have a component with a child frequently re-rendering, and you pass a callback to it:

import React, { useState, useCallback } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0)
  const [otherCounter, setOtherCounter] = useState(0)

  const increment = () => {
    setCount(count + 1)
  }
  const decrement = () => {
    setCount(count - 1)
  }
  const incrementOtherCounter = () => {
    setOtherCounter(otherCounter + 1)
  }

  return (
    <>
      Count: {count}
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={incrementOtherCounter}>incrementOtherCounter</button>
    </>
  )
}

ReactDOM.render(<Counter />, document.getElementById('app'))

The problem here is that any time the counter is updated, all the 3 functions are re-created again.

You can visualize this by instantiating a Set data structure, and adding each function to it. Why Set? because it only stores unique elements, which in our case means different (uniquely instantiated) functions.

import React, { useState, useCallback } from 'react'

const functionsCounter = new Set()

const Counter = () => {
  const [count, setCount] = useState(0)
  const [otherCounter, setOtherCounter] = useState(0)

  const increment = () => {
    setCount(count + 1)
  }
  const decrement = () => {
    setCount(count - 1)
  }
  const incrementOtherCounter = () => {
    setOtherCounter(otherCounter + 1)
  }

  functionsCounter.add(increment)
  functionsCounter.add(decrement)
  functionsCounter.add(incrementOtherCounter)

  alert(functionsCounter)

  return (
    <>
      Count: {count}
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={incrementOtherCounter}>incrementOtherCounter</button>
    </>
  )
}

ReactDOM.render(<Counter />, document.getElementById('app'))

If you try out this code you’ll see the alert incrementing by 3 at a time.

What should happen instead it’s that if you increment one counter, all functions related to that counter should be re-instantiated.

If another state value is unchanged, it should not be touched.

Now, in most cases this is not a huge problem unless you are passing lots of different functions, all changing unrelated bits of data, that are proven to be a big cost for your app performance.

If that’s a problem, you can use useCallback.

This is how we do it. Instead of:

const increment = (() => {
  setCount(count + 1)
})
const decrement = (() => {
  setCount(count - 1)
})
const incrementOtherCounter = (() => {
  setOtherCounter(otherCounter + 1)
})

You wrap all those calls in:

const increment = useCallback(() => {
  setCount(count + 1)
}, [count])
const decrement = useCallback(() => {
  setCount(count - 1)
}, [count])
const incrementOtherCounter = useCallback(() => {
  setOtherCounter(otherCounter + 1)
}, [otherCounter])

Make sure you add that array as a second parameter to useCallback() with the state needed.

Now if you try to click one of the counters, only the functions related to the state that changes are going to be re-instantiated.

You can try this example on Codepen:

See the Pen React useCallback hook by Flavio Copes (@flaviocopes) on CodePen.



I write books for developers 👇🏼

© 2023 Flavio Copes Flavio Copes made in Italy 🇮🇹 using Notion to Site Notion to Site