When to use useCallback - ReactJS?

boywithsilverwings

Agney Menon

Posted on October 23, 2019

When to use useCallback - ReactJS?

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.
Enter fullscreen mode Exit fullscreen mode

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
});

Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

This would refractor child to:

import React, { memo } from "react";

const Child = memo(({ reset }) => {
  console.log("re-render child component.")
  return (
    <div>
      <p>child component which resets count</p>
      <button onClick={reset}>Reset Count</button>
    </div>
  );
});

export default Child;
Enter fullscreen mode Exit fullscreen mode

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

const Parent () => {
  const [count, setCount] = useState(0);
  console.log("re-render parent component");

  const resetCount = () => {
    setCount(0);
  };
  return (
    <main>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count=>(count+1))}>Increment</button>
      <Child reset={resetCount} />
    </main>
  )
}
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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:

Crossposted from my blog

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?
💖 💪 🙅 🚩
boywithsilverwings
Agney Menon

Posted on October 23, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related