useMemo Hook in React | What is it and how and when to use it?
— React, Web Development, Hooks — 6 min read
The useMemo
hook is a built-in hook in React. It is used to cache or memoize the result of an expensive operation against a given set of inputs to speed up future re-renders provided the inputs remain the same.
In this tutorial, we're going to look more into what useMemo
is and how and when to use it.
What is the meaning of "memo" in useMemo
?
Memo is short for memoization.
What is memoization?
As per Wikipedia:
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls to pure functions and returning the cached result when the same inputs occur again.
So with useMemo
, you're telling React to "remember" the result of an expensive operation for a given set of inputs.
How to determine whether an operation is expensive?
As per the official React documentation, if the operation is taking more than 1ms overall, then it qualifies as an expensive calculation.
In real-life application development, this may happen when you're looping over an array of hundreds or thousands of items, trying to filter, map, reverse or sort it.
How to use useMemo
?
Lets see useMemo
in action in an example.
Consider the below <App />
component. It renders a simple "Counter" and an "Increment" button to increment the counter. It does this by storing the counter value into a state variable called count
.
The other interesting thing about this component is that before rendering it performs an expensive operation by invoking doExpensiveOperation()
which involves reversing a rather large array of objects. This is just for demonstration purposes to simulate a slow and expensive calculation.
import { useState } from "react";
export default function App() { const [ count, setCount ] = useState( 0 );
doExpensiveOperation();
return ( <> <p>Counter: {count}</p> <button type="button" onClick={() => setCount( count+1 )}>Increment</button> </> );}
function doExpensiveOperation(){ console.time(); const hugeArrOfNums = Array.from(Array(99_99_999).keys()); const hugeArrOfObjs = hugeArrOfNums.map( i => ({ id: i+1 }) ); const reverseArray = hugeArrOfObjs.reverse(); console.timeEnd(); return reverseArray;}
If you click the "Increment" button, you'll notice that the updated count doesn't get rendered immediately. In fact, it gets rendered after a significant delay.
If you check the console logs, you'll see that the delay is almost 1 second. Please note that there are two console logs for each Increment button click because of double re-renders in StrictMode
.
This delay happens because when you hit "Increment", the count
state value gets incremented. This causes the <App />
component to re-render. A re-render causes all of the code inside <App />
to run again which means doExpensiveOperation()
runs again and blocks the return
from returning JSX until the expensive operation is complete. This delays the re-rendering which results in bad UX.
Lets use useMemo
and see how it affects the app's performance.
import { useMemo, useState } from "react";
export default function App() { const [ count, setCount ] = useState( 0 );
useMemo( () => { doExpensiveOperation(); }, [] );
return ( <> <p>Counter: {count}</p> <button type="button" onClick={() => setCount( count+1 )}>Increment</button> </> );}
function doExpensiveOperation(){ console.time(); const hugeArrOfNums = Array.from(Array(99_99_999).keys()); const hugeArrOfObjs = hugeArrOfNums.map( i => ({ id: i+1 }) ); const reverseArray = hugeArrOfObjs.reverse(); console.timeEnd(); return reverseArray;}
The format for using useMemo
is:
useMemo( () => { /* expensive operation */ }, /* dependency array */ )
So in our example, we wrap the code that was causing our app to slowdown with an anonymous arrow function and pass that in as the first input argument. This function does not take any input arguments and can return anything you want. It should also be a pure function meaning it should always return the same result for a given set of inputs.
The second input argument is the dependency array which in this case is blank.
useMemo( () => { doExpensiveOperation();}, [] );
Now if you reload the page and click on "Increment", you'll notice that the incremented count gets rendered immediately without any delays.
If you check the console log, you'll notice that there is just one( or two in StrictMode
). This is from the initial render. During the initial render, React runs the expensive operation once and then caches it. The next time React needs to run that function, it will check the inputs(which in this case are none), and if they're the same, it'll directly return the memoized value instead of running that function again.
Specifying dependencies in useMemo
If there are state values or any other reactive values being used within useMemo
then we must specify them as dependecies in the dependency array which is the second argument.
For example:
...const [ name, setName ] = useState( "" );
useMemo( () => { doExpensiveOperation(); console.log( name );}, [ name ] );...
When should you use useMemo
?
As mentioned previously in this article, if an operation is expensive( i.e. it takes more than 1ms overall ) then do consider wrapping it within a useMemo
. It is not recommended to use useMemo
for every operation or for operations that are not expensive because then it'll become overkill and the overhead of using useMemo
will most probably make the whole thing more inefficient than what it was before.
I am using useMemo
but my first render is still slow?
This is expected. As mentioned above, during the first render, React runs the function within useMemo
against the dependencies and inputs and caches it. The next time React needs to run the operation for the same inputs, it directly returns the memoized value and skips running the operation. So the first render will be slow but then subsequent renders will be faster.
I am using useMemo
but most of the re-renders are still slow?
This may happen if the dependencies change very frequently. This frequent change invalidates the memoized value from the previous render and React has to run the expensive operation that many times which effectively makes it redundant to use useMemo
.
In this case, identify such dependencies and either try to remove them as a dependency or make them change less frequently.
From our previous example, if the count
state value were to become a dependency within useMemo
, everytime "Increment" is clicked, it'll cause React to run doExpensiveOperation()
since the dependency count
will be different each time. This will cause delays everytime "Increment" was clicked which will effectively be the same behaviour as when useMemo
wasn't being used.
const [ count, setCount ] = useState( 0 );
useMemo( () => { doExpensiveOperation(); console.log( count );}, [ count ] );...
Summary
- The
useMemo
hook is a built-in hook in React which is used to cache or memoize the result of an expensive operation against a given set of inputs to speed up future re-renders provided the inputs remain the same. - Use
useMemo
only when a calculation is really expensive i.e. if it takes more than 1ms or more overall. - Make sure the
useMemo
dependencies don't change very often because otherwise it will makeuseMemo
less effective.
Hope this helps!🙏