React ‘useMemo’ and ‘useCallback’: When to use them and when not
We’ve all been told to use the React hooks useMemo
and useCallback
to improve performance by having React ‘remember’ complex calculations. Whenever we wrap a considerable amount of logic into these hooks, do they actually improve performance?
In this blog, we’ll learn what these hooks do and run some experiments to learn their effect on overall performance.
What are the useMemo
and useCallback
hooks? And how do they work?
useMemo
, according to the official React documentation, “Returns a memoized value”. A usage example would be:
Memoization, in the form of useMemo
in the above example, is used to help improve performance.
On the first render, React executes and ‘remembers’ the returned value of that function. In re-renders, React uses the ‘remembered’ value instead of executing the function again (unless the values in provided dependencies array have changed, such as in the above example, unsortedList
). When the function is computationally expensive, not executing it every render could help improve performance.
In the example given above, instead of getting unsortedList
every time in re-renders, React would simply remember the result from the first render and use it again, provided that unsortedList
didn’t change.
useCallback
works similarly. Instead of memoizing the result from executing a function, it memoizes a provided function object. This potentially improves performance by not having to re-create the function object every time the state changes. This is particularly useful when the function is passed to a child component as it could prevent re-renders caused by changes in the function.
As you can see from the example above, the callApi
function will be memoized. Instead of recreating this function in every render, React will reuse the same instance of the function as long as userId
didn’t change.
If the callApi
function isn’t memoized and is passed into a memoized child component, the child component will still re-render every time the parent component re-renders, even though it’s memoized. This is because the function created by the parent component will be different every time. With useCallback
, the child component — assuming that it’s memoized - would not re-render as long as other props didn’t change.
In short, useMemo
is used to remember values, reducing the time needed to re-render a component. useCallback
is used to remember functions, usually to prevent re-renders of components.
useMemo doesn’t always help improve performance
Here’s a little experiment to help us find out if this statement is true. By sorting an array of random numbers, we can highlight the relationship between code logic complexity and performance improvement by using useMemo.
The following results do align with an article from 2019 by Kevin performing similar experiments.
This setup ran with 2 variations:
- With or without
useMemo
- Size of the array of random numbers
These tests were run in the production build with the profiler enabled, and each experiment was run three times to get an average result. The results came in as follows:
Each chart corresponds to one set of experiments with the same array size (100–1,000,000). The orange column represents the render time with useMemo
, while the yellow column represents it without.
Note: The initial render time includes time spent to render the HTML elements and create the random array, but as this is the same for both with and without useEffect, it is still a fair test.
For small operations, useMemo
does cause some overhead. The advantages of useMemo
become apparent as the calculations increase in complexity - notably after the 1,000+ length point.
(The code used in this experiment can be found here: gabrielleecredera/react-memoization-hooks-performance-test. A very basic UI is included to help set up the experiments.)
useMemo and useCallback could make your code less readable
Every pro has a con, and one drawback is that excessive use of the useMemo
and useCallback
hooks could make your code less readable. Not only do they bloat the codebase, but you also have to actively maintain the dependencies array, because the value or function object might not update when you’d expect it to.
Have a look at the following piece of code that renders the input unsortedList
as a sorted list:
If the input is not too long (e.g. <200 elements), we’ve proven in the above section that we actually don’t need to use useMemo
. As such, we can do this instead:
Not only does this make the code easier to read, but it also reduces the chance of a missing dependency in the dependency array when we make changes to the function we provide to useMemo
.
Conclusion
useMemo
and useCallback
reduce re-renders and render time in complex calculations. However, there are situations where the cons outweigh the pros. The next time you are faced with using this solution, think about the pros and cons — especially when dealing with relatively simple calculations.
Useful links
- https://kentcdodds.com/blog/usememo-and-usecallback: a deep dive useMemo, useCallback and other react hooks
- https://canimerge.com/should-you-really-use-usememo-in-react-lets-find-out/: an article from 2019 performing similar experiments
Interested in joining us?
Credera is currently hiring! View our open positions and apply here.
Got a question?
Please get in touch to speak to a member of our team.