Demystifying useCallback() hook! When to use it?

MVS KIRAN
4 min readJan 9, 2023

UseCallback is a hook that returns a memoized version of a callback function that only changes when one of its dependencies changes.

Frequently used words: Memoization, Reference equality

“Every time your component is re-render, A fresh copy of the functions is newly created with a new reference. [Functions inside the component are garbage collected and re-created on every render ].”

Before jumping into useCallback() directly, let's understand the use of React.memo().

React.memo()

When deciding to update DOM, React first renders your component, then compares the result with the previous render. If the render results are different, React updates the DOM.

Current vs previous render results in comparison is fast. But you can speed up the process under some circumstances.

When a component is wrapped in React.memo(), React renders the component and memoizes the result. Before the next render, if the new props are the same, React reuses the memoized result skipping the next rendering. credits: Dmitripavlutin

Let's take an example:

Let's say we have two child components and we are passing state values as a prop to the child components.

If we wrap the Counter1 component [attached in the below image] while exporting with React.memo() [line no 9 in the second image] then if passed props were not changed then the component is not re-rendered. React. memo() will do the shallow comparison of props and check if previous props are equal to current props.

Thereby reducing the unnecessary re-render of child components.

Now let's say we want to pass functions, arrays, or objects as props.React.memo() works based on reference equality. So, every time our component renders the functions will be re-created and a new reference is generated and stored in heap memory. So, we are passing the callback function as a prop to the child components and every time a new function is created and its reference is not the same hence child component will receive the callback function with the new reference and the child component will render since it compares the previous callback function reference and it is not equal.

Final solutions to avoid unnecessary re-render of child component due to reference equality :

So, In order to restrict the functions [line no 14 and 17 on the below image] from being re-created every time the component re-render, we will wrap it with the useCallback() hook by providing the dependencies. So, It will only re-create the function if and only if any one of its dependencies is changed.

It's a best practice to wrap the function with useCallback() when passing that callback to optimized child components that rely on reference equality to prevent unnecessary renders.

Codesandbox links: https://codesandbox.io/s/usecallback-memo-5dpwfp?file=/src/App.js

If you look at the below output when you click on the counter1 + button, only counter1 is rendered and the counter2 component is not re-rendered. Since we have wrapped with the useCallback() hook and it won’t re-create and re-reference the actual function if none of its dependencies are changed.

If we want to cross-check it further here is a Codesandbox exercise in which you can test whether functions are re-created when not wrapped with useCallback () using a set data structure.

👉🏻 https://codesandbox.io/s/debugging-usecallback-forked-x5wxsq?file=/src/App.js:0–1715

🚀 Pros and cons :
Every pro has a con, and one drawback is that excessive use of theuseCallback hooks could make your code less readable. Not only do they bloat the codebase, but you also have to actively maintain the dependencies array.

🎬 Conclusion :

I’d just like to wrap this up by saying that every abstraction (and performance optimization) comes at a cost. Do profiling and think of its use case and complexity before it wraps with useCallback(). I have seen many codebases that are wrapped with functions with useCallback() without proper knowledge.

Moreover, The incorrect use of useCallback are that you make the code more complex for your co-workers, and less readable. You could make a mistake in the dependencies array. Also, you're potentially making performance worse by invoking the built-in hooks and preventing dependencies and memoized values from being garbage collected.

--

--