Categories
reactjs

🐱‍👤When to use the useCallback hook ?

Hey everyone 👋🏻,

In this article, let us learn about a very special React Hook called as the useCallback hook and understand how and when to use it.

What is the useCallback hook ?

useCallback is a React Hook which was introduced to prevent re-creation of a specific function in case our component re-renders.
It does so by memoizing the callback function that this hook takes and this memoized version will only change when one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.

So typically we should make use of the useCallback hook in one of the following cases :

  1. If you are passing the function on to child component as props and the child component doesn’t often need re-rendering except when a certain prop change then useCallback might prevent certain re-renders.
  2. You are specifying a function as a dependency to useEffect. In such a case you must ensure that the function is not recreated on every render or the useEffect will be triggered on every render.

So in the essence, the decision to make use of the useCallback must be made judiciously instead of blindly since you might just overdo the advantage offered by useCallback and end up degrading the performance since useCallback will also memoize the functions and a frequently changing dependency might anyways need to recreate the function.

If the dependencies of the useCallback change, only then the function will get recreated otherwise it will use the exact one and the same instance of that function across the re-renders.

Example :

In JavaScript, we know that when a function gets executed we know that each and every thing (variables functions, objects etc) that is present within the function gets re-created into the memory. So this happens on each execution of the function. Now for a function we might not want it to get re-created on each re-render of the component and in turn we may want to use the exact same instance for that specific function in each of our renders. We might only want to re-create the function when a specific dependency which that function uses gets changed and NOT otherwise. This is something that we can achieve by making use of the useCallback hook.

Let’s see a simple Counter component.
Here we have a single piece of state which we call as counter and we have a corresponding state updating function for it which we named as setCounter. The initial value for counter is given as 0.
Next we have a handleIncrement counter function and all it does is that it increments the value of counter by 1.

const Counter = () => {
  const [counter, setCounter] = useState(0);

  const handleIncrement = () => {
    setCounter(counter + 1);
  }

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleIncrement}>Increment</button>
  </div>
}

Now the interesting thing to note here is that the handleIncrement function will get re-created on each render of the component. This means that each time the component re-renders, handleIncrement function will get re-created at a separate spot in memory. And due to this reason, when React tries to make a referential equality comparison, it finds a change because of the re-creation of the handleIncrement function.

Now what if we wrap this with the useCallback hook ?

What the useCallback does is that it internally caches the first created version of the function and returns it to the caller if the listed dependencies haven’t changed. In case the listed dependencies does change, then it automatically gives us a new instance of that function.
So if we introduce the useCallback hook into our above code, then our code should look something like this :

const Counter = () => {
  const [counter, setCounter] = useState(0);

  const handleIncrement = React.useCallback(() => {
    setCounter(counter + 1);
  }, [])

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleIncrement }>Increment</button>
  </div>
}

Now with the above code, what React will do is that it will cache the handleIncrement function internally and will make sure to return the exact same instance of the function in case no external dependencies of the useCallback have changed. In case, the external dependencies do change, then in that case a brand new instance of the handleIncrement function will get created for us. For our case, there are no external dependencies for the handleIncrement function.

Now caching the function internally by using the useCallback has its own cons. The cached instance of the function call will not have direct access to the variables of our current function call. Instead of that it will have visibility of the variables that were introduced in the initial closure call where the stored function was created. So in essence, this means that our call will not work for the updated variables. Therefore, we need to specify those variables or dependencies of the function that can change. The list of dependencies is to be passed as the second argument of the useCallback. In our example, we need to tell to useCallback function that we need a fresh version of our counter variable on each call. If we don’t do that, then the counter variable after the call will always be 1 and this is what comes from the original value 0 plus 1.

const Counter = () => {
  const [counter, setCounter] = useState(0);

  const handleIncrement = React.useCallback(() => {
    setCounter(counter + 1);
  }, [counter])

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleIncrement}>Increment</button>
  </div>
}

The above version of code will not re-render on every call.

So this is it for this article. Thanks for reading.

If you enjoy my articles, consider following me on Twitter for more interesting stuff :

Image description

⚡Twitter : https://twitter.com/The_Nerdy_Dev

Don’t forget to leave a like if you loved the article. Also share it with your friends and colleagues.

Alt Text