useMemo
useMemo
is a React Hook that lets you skip recalculating a value on a re-render.
const value = useMemo(calculateValue, dependencies)
Usage
Skipping expensive recalculations
By default, React will re-run the entire body of your component every time that it re-renders. For example, if this TodoList
updates its state or receives new props from its parent, the filterTodos
function will re-run:
function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}
Usually, this isnât a problem because most calculations are very fast. However, if youâre filtering or transforming a large array, or doing some other expensive computation, you might want to skip doing it again unless something has changed. If both todos
and tab
are the same as they were during the last render, you can instruct React to reuse the visibleTodos
youâve already calculated during the last render. This technique is called memoization.
To declare a memoized value, wrap your calculation into a useMemo
call at the top level of your component:
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
You need to pass two things to useMemo
:
- A calculation function that takes no arguments, like
() =>
, and returns what you wanted to calculate. - A list of dependencies including every value within your component thatâs used inside your calculation.
On the initial render, the value youâll get from useMemo
will be the result of calling your calculation.
On every next render, React will compare the dependencies with the dependencies you passed during the last render. If none of the dependencies have changed (compared with Object.is
), useMemo
will return the value you already calculated on the last render. Otherwise, React will re-run your calculation and return the new value.
You should only rely on this feature as a performance optimization. If your code doesnât work at all without it, find the underlying problem and fix the code first, and only then add memoization to improve the performance.
Deep Dive
How to tell if a calculation is expensive?
How to tell if a calculation is expensive?
In general, unless youâre creating or looping over thousands of objects, itâs probably not expensive. If you want to get more confidence, you can add a console log to measure the time spent in a piece of code:
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
Perform the interaction youâre measuring (for example, typing into the input). You will then see logs like filter array: 0.15ms
in your console. If the overall logged time adds up to a significant amount (say, 1ms
or more), it might make sense to memoize that calculation. As an experiment, you can then wrap the calculation in useMemo
to verify whether the total logged time has decreased for that interaction or not:
console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Skipped if todos and tab haven't changed
}, [todos, tab]);
console.timeEnd('filter array');
useMemo
wonât make the first render faster. It only helps you skip unnecessary work on updates.
Keep in mind that your machine is probably faster than your usersâ so itâs a good idea to test the performance with an artificial slowdown. For example, Chrome offers a CPU Throttling option for this.
Also note that measuring performance in development will not give you the most accurate results. (For example, when Strict Mode is on, you will see each component render twice rather than once.) To get the most accurate timings, build your app for production and test it on a device like your users have.
Example 1 of 2: Skipping recalculation with useMemo
In this example, the filterTodos
implementation is artificially slowed down so that you can see what happens when some JavaScript function youâre calling during rendering is genuinely slow. Try switching the tabs and toggling the theme.
When you switch the tabs, filterTodos
gets called. Thatâs expected because the tab
has changed. (It also gets called twice in development, but you should ignore this. React calls your components twice during development to help find impure code.)
Notice that when you switch the theme toggle, filterTodos
does not get called. This is because both todos
and tab
(which you pass as dependencies to useMemo
) are the same as they were during the last render. This is what useMemo
enables.
import { useMemo } from 'react'; import { filterTodos } from './utils.js' export default function TodoList({ todos, theme, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); return ( <div className={theme}> <ul> {visibleTodos.map(todo => ( <li key={todo.id}> {todo.completed ? <s>{todo.text}</s> : todo.text } </li> ))} </ul> </div> ); }
Skipping re-rendering of components
By default, when a component re-renders, React re-renders all of its children recursively. This is fine for components that donât require much calculation to re-render. Components higher up the tree or slower components can opt into skipping re-renders when their props are the same by wrapping themselves in memo
:
import { memo } from 'react';
function List({ items }) {
// ...
}
export default memo(List);
For this optimization to work, the parent component that renders this <List />
needs to ensure that, if it doesnât want List
to re-render, every prop it passes to the List
must be the same as on the last render.
Letâs say the parent TodoList
component looks like this:
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
With the above code, the List
optimization will not work because visibleTodos
will be a different array on every re-render of the TodoList
component. To fix it, wrap the calculation of visibleTodos
in useMemo
:
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
After this change, as long as todos
and tab
havenât changed, thanks to useMemo
, the visibleTodos
wonât change between re-renders. Since List
is wrapped in memo
, it will only re-render if one of its props is different from its value on the last render. Youâre passing the same items
prop, so List
can skip the re-rendering entirely.
Notice that in this example, it doesnât matter whether filterTodos
itself is fast or slow. The point isnât to avoid a slow calculation, but itâs to avoid passing a different prop value every time since that would break the memo
optimization of the child List
component. The useMemo
call in the parent makes memo
work for the child.
Example 1 of 2: Skipping re-rendering with useMemo
and memo
In this example, the List
component is artificially slowed down so that you can see what happens when a React component youâre rendering is genuinely slow. Try switching the tabs and toggling the theme.
When you switch the tabs, <List />
gets re-rendered. Changing the tab
causes the visibleTodos
to be recreated. Since the items
passed to the List
are a different array from the items
passed to List
on last render, the List
must re-render.
However, when you switch the theme toggle, <List />
does not re-render. This is because both todos
and tab
(which you pass as dependencies to useMemo
) are the same as they were during the last render. This makes the visibleTodos
the same as on the last render. In List.js
, the List
component is wrapped in memo
, so it skips re-rendering for the same items
.
import { useMemo } from 'react'; import List from './List.js'; import { filterTodos } from './utils.js' export default function TodoList({ todos, theme, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); return ( <div className={theme}> <List items={visibleTodos} /> </div> ); }
Memoizing a dependency of another Hook
Suppose you have a calculation that depends on a searchOptions
object:
function Dropdown({ allItems, text }) {
const searchOptions = {
matchMode: 'whole-word',
text: text
};
const visibleItems = searchItems(allItems, searchOptions);
// ...
}
You try to memoize the result of calling searchItems
:
function Dropdown({ allItems, text }) {
const searchOptions = {
matchMode: 'whole-word',
text: text
};
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // đ´ Memoization doesn't work: searchOptions is always new
// ...
However, this doesnât improve anything: searchItems
would still get called on each render.
Hereâs why this happens. Every time that a component re-renders, all of the code directly inside the component body runs again. The lines of code creating a new searchOptions
object will also run on every re-render. Since searchOptions
is a dependency of your useMemo
call, and itâs different every time, React will consider the dependencies as being different from the last time, and will have to call your searchItems
function again.
When you can, itâs best to fix this by moving the object creation inside the useMemo
call:
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = {
matchMode: 'whole-word',
text: text
};
return searchItems(allItems, searchOptions);
}, [allItems, text]); // â
Memoization works
// ...
Now your calculation depends on text
(which is a string and canât be unintentionally new).
Alternatively, you could memoize the searchOptions
object itself before passing it as a dependency:
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]); // â
Memoization works
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // â
Memoization works
// ...
In this example, the searchOptions
only gets recalculated when the text
changes. Then, the visibleItems
only gets recalculated if either allItems
or searchOptions
changes.
You can use a similar approach to prevent useEffect
from firing again unnecessarily. However, there are usually better solutions than wrapping Effect dependencies in useMemo
. Read about removing Effect dependencies.
Reference
useMemo(calculateValue, dependencies)
Call useMemo
at the top level of your component to declare a memoized value:
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...
}
Parameters
calculateValue
: The function calculating the value that you want to memoize. It should be pure, should take no arguments, and should return a value of any type. React will call your function during the initial render. On subsequent renders, React will return the same value again if thedependencies
have not changed since the last render. Otherwise, it will callcalculateValue
, return its result, and store it in case it can be reused later.dependencies
: The list of all reactive values referenced inside of thecalculateValue
code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like[dep1, dep2, dep3]
. React will compare each dependency with its previous value using theObject.is
comparison algorithm.
Returns
On the initial render, useMemo
returns the result of calling calculateValue
with no arguments.
During subsequent renders, it will either return an already stored value from the last render (if the dependencies havenât changed), or call calculateValue
again, and return the result that calculateValue
has returned.
Caveats
useMemo
is a Hook, so you can only call it at the top level of your component or your own Hooks. You canât call it inside loops or conditions. If you need that, extract a new component and move the state into it.- In Strict Mode, React will call your calculation function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your calculation function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored.
Troubleshooting
My calculation runs twice on every re-render
In Strict Mode, React will call some of your functions twice instead of once:
function TodoList({ todos, tab }) {
// This component function will run twice for every render.
const visibleTodos = useMemo(() => {
// This calculation will run twice if any of the dependencies change.
return filterTodos(todos, tab);
}, [todos, tab]);
// ...
This is expected and shouldnât break your code.
This development-only behavior helps you keep components pure. React uses the result of one of the calls, and ignores the result of the other call. As long as your component and calculation functions are pure, this shouldnât affect your logic. However, if they are accidentally impure, this helps you notice the mistakes and fix it.
For example, this impure calculation function mutates an array you received as a prop:
const visibleTodos = useMemo(() => {
// đŠ Mistake: mutating a prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);
Because React calls your calculation twice, youâll see the todo was added twice, so youâll know that there is a mistake. Your calculation canât change the objects that it received, but it can change any new objects you created during the calculation. For example, if filterTodos
always returns a different array, you can mutate that array:
const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// â
Correct: mutating an object you created during the calculation
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);
Read keeping components pure to learn more about purity.
Also, check out the guides on updating objects and updating arrays without mutation.
My useMemo
call is supposed to return an object, but returns undefined
Be careful when returning an object from an arrow function. This code works:
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);
This code doesnât work:
// đ´ You can't return an object from an arrow function with () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);
In JavaScript, () => {
starts the arrow function body, so the {
brace is not a part of your object. This is why it doesnât return an object, and leads to confusing mistakes. You could fix it by adding parentheses:
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);
However, itâs much clearer to write a return
statement explicitly.
Every time my component renders, the calculation in useMemo
re-runs
Make sure youâve specified the dependency array as a second argument!
If you forget the dependency array, useMemo
will re-run the calculation every time:
function TodoList({ todos, tab }) {
// đ´ Recalculates every time: no dependency array
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...
This is the corrected version passing the dependency array as a second argument:
function TodoList({ todos, tab }) {
// â
Does not recalculate unnecessarily
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
If this doesnât help, then the problem is that at least one of your dependencies is different from the previous render. You can debug this problem by manually logging your dependencies to the console:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
You can then right-click on the arrays from different re-renders in the console and select âStore as a global variableâ for both of them. Assuming the first one got saved as temp1
and the second one got saved as temp2
, you can then use the browser console to check whether each dependency in both arrays is the same:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // Is the third dependency the same between the arrays?
When you find which dependency is breaking memoization, either find a way to remove it, or memoize it as well.