Skip to content

How to use the useCallback React hook

Find out what the useCallback React hook is useful for, and how to work with it!

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: https://codepen.io/flaviocopes/pen/VJQwzb/


→ Get my React Beginner's Handbook

download all my books for free

  • javascript handbook
  • typescript handbook
  • css handbook
  • node.js handbook
  • astro handbook
  • html handbook
  • next.js pages router handbook
  • alpine.js handbook
  • htmx handbook
  • react handbook
  • sql handbook
  • git cheat sheet
  • laravel handbook
  • express handbook
  • swift handbook
  • go handbook
  • php handbook
  • python handbook
  • cli handbook
  • c handbook

subscribe to my newsletter to get them

Terms: by subscribing to the newsletter you agree the following terms and conditions and privacy policy. The aim of the newsletter is to keep you up to date about new tutorials, new book releases or courses organized by Flavio. If you wish to unsubscribe from the newsletter, you can click the unsubscribe link that's present at the bottom of each email, anytime. I will not communicate/spread/publish or otherwise give away your address. Your email address is the only personal information collected, and it's only collected for the primary purpose of keeping you informed through the newsletter. It's stored in a secure server based in the EU. You can contact Flavio by emailing [email protected]. These terms and conditions are governed by the laws in force in Italy and you unconditionally submit to the jurisdiction of the courts of Italy.

Related posts about react: