React ‘useMemo’ and ‘useCallback’: When to use them and when not

Gabriel Lee
Credera Engineering
5 min readSep 20, 2022

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.

Infographic highlighting the high-level difference between using and not using useMemo()
The high-level difference between using and not using useMemo()

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:

  1. With or without useMemo
  2. 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.

Charts showing render times with and without using useMemo()
Another set of charts highlighting the difference in performance as the calculation complexity (array size) increases (in log scale)

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

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.

--

--