When to use useCallback

Posted on October 23, 2019

This post does not have a Yes/No answer, but tries to explain from a begineer standpoint why this hook is part of the official roster.

Let us start the story with two components:

  1. Parent
  2. Child

The parent component has a button that increments the count state in the same component while Child component will have nothing to do with it.

Note the console logs as you click re-render. Both child and parent will re-render with logs:

re-render parent component
re-render child component.

even though the child component has nothing to do with the state at all.

Now, we have to prevent the Child component from rerendering. Keeping the functional component we can use React.memo to achieve this. The child component will become:

import React, { memo } from 'react';
const Child = memo(({ reset }) => {
// same content as earlier
});

Without the second argument, memo will do a shallow comparison of props:

if (prevProps !== props) {
rerender();
} else {
// don't
}

You can check the logs now and see that it does not update the child component on parent rerender. It only updates the parent component with the log:

re-render parent component

Now, the requirements have progressed and we have to house a Reset button for count inside the Child component.

This would refractor child to:

1import React, { memo } from 'react';
2
3const Child = memo(({ reset }) => {
4 console.log('re-render child component.');
5 return (
6 <div>
7 <p>child component which resets count</p>
8 <button onClick={reset}>Reset Count</button>
9 </div>
10 );
11});
12
13default Child;

For the reset function we have to refractor the parent to:

1const Parent () => {
2 const [count, setCount] = useState(0);
3 console.log("re-render parent component");
4
5 const resetCount = () => {
6 setCount(0);
7 };
8 return (
9 <main>
10 <p>Count: {count}</p>
11 <button onClick={() => setCount(count=>(count+1))}>Increment</button>
12 <Child reset={resetCount} />
13 </main>
14 )
15}

Now you can click on the reset button to reset the count to 0. But you will notice that the memo magic that we applied earlier is not working anymore. The logs suggest that both child and parent are being rerendered. Why is this happening?

As we mentioned earlier, memo depends on the referential equality of prevProps and props to work. But the resetCount function is being created on every render of Parent and hence prevProps and props is not the same anymore (even though they are).

Now, to apply the memo magic again, we need to make sure that resetCount function is not unnecessarily recreated on every render of Parent. This is exactly what useCallback helps us to do.

const resetCount = useCallback(() => {
setCount(0);
}, [setCount]);

useCallback will always return the same instance of the function on re-renders and would refresh only when dependencies change. Note the second argument of useCallback, this is very similar to the useEffect hook and refers to the dependecies that should trigger a reintialisation of the function inside useCallback hook.

Finished Demo:

Extended Reading:

  1. useCallback Docs
  2. When to use useMemo and useCallback - Kent C Dodds
  3. How to read an often-changing value from useCallback?
  4. Are Hooks slow because of creating functions in render?