Reactâs useCallback Hook can be used to optimize the rendering behavior of your React function components . We will go through an example component to illustrate the problem first, and then solve it with Reactâs useCallback Hook .
Keep in mind that most of the performance optimizations in React are premature. React is fast by default, so every performance optimization is opt-in in case something starts to feel slow.
Note: Donât mistake Reactâs useCallback Hook with Reactâs useMemo Hook . While useCallback is used to memoize functions, useMemo is used to memoize values.
Note: Donât mistake Reactâs useCallback Hook with Reactâs memo API . While useCallback is used to memoize functions, React memo is used to wrap React components to prevent re-renderings.
Letâs take the following example of a React application which renders a list of user items and allows us to add and remove items with callback handlers . We are using Reactâs useState Hook to make the list stateful:
Using what we learned about React memo (if you donât know React memo, read the guide first and then come back), which has similar components to our example, we want to prevent every component from re-rendering when a user types into the input field.
Typing into the input field for adding an item to the list should only trigger a re-render for the App component, but not for its child components which donât care about this state change. Thus, React memo will be used to prevent the child components from updating:
However, perhaps to your surprise, both function components still re-render when typing into the input field. For every character thatâs typed into the input field, you should still see the same output as before:
Letâs have a look at the props that are passed to the List component.
As long as no item is added or removed from the list prop, it should stay intact even if the App component re-renders after a user types something into the input field. So the culprit is the onRemove callback handler.
Whenever the App component re-renders after someone types into the input field, the handleRemove handler function in the App gets re-defined.
By passing this new callback handler as a prop to the List component, it notices a prop changed compared to the previous render . Thatâs why the re-render for the List and ListItem components kicks in.
Finally we have our use case for Reactâs useCallback Hook. We can use useCallback to memoize a function , which means that this function only gets re-defined if any of its dependencies in the dependency array change:
If the users state changes by adding or removing an item from the list, the handler function gets re-defined and the child components should re-render.
However, if someone only types into the input field, the function doesnât get re-defined and stays intact. Therefore, the child components donât receive changed props and wonât re-render for this case.
You might be wondering why you wouldnât use Reactâs useCallback Hook on all your functions or why Reactâs useCallback Hook isnât the default for all functions in the first place.
Internally, Reactâs useCallback Hook has to compare the dependencies from the dependency array for every re-render to decide whether it should re-define the function. Often the computation for this comparison can be more expensive than just re-defining the function.
In conclusion, Reactâs useCallback Hook is used to memoize functions. Itâs already a small performance gain when functions are passed to others components without worrying about the function being re-initialized for every re-render of the parent component. However, as you have seen, Reactâs useCallback Hook starts to shine when used together with Reactâs memo API.