useMemo is a React Hook that lets you cache the result of a calculation between re-renders.
constcachedValue =useMemo(calculateValue,dependencies)Note
React Compiler automatically memoizes values and functions, reducing the need for manualuseMemo calls. You can use the compiler to handle memoization automatically.
Reference
useMemo(calculateValue, dependencies)
CalluseMemo at the top level of your component to cache a calculation between re-renders:
import{useMemo}from'react';
functionTodoList({todos,tab}){
constvisibleTodos =useMemo(
()=>filterTodos(todos,tab),
[todos,tab]
);
// ...
}Parameters
calculateValue: The function calculating the value that you want to cache. 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 next renders, React will return the same value again if thedependencieshave not changed since the last render. Otherwise, it will callcalculateValue, return its result, and store it so it can be reused later.dependencies: The list of all reactive values referenced inside of thecalculateValuecode. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter isconfigured 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.iscomparison.
Returns
On the initial render,useMemo returns the result of callingcalculateValue with no arguments.
During next renders, it will either return an already stored value from the last render (if the dependencies haven’t changed), or callcalculateValue again, and return the result thatcalculateValue has returned.
Caveats
useMemois a Hook, so you can only call itat 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 willcall your calculation function twice in order tohelp 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 your logic. The result from one of the calls will be ignored.
- Reactwill not throw away the cached value unless there is a specific reason to do that. For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache—for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should be fine if you rely on
useMemosolely as a performance optimization. Otherwise, astate variable or aref may be more appropriate.
Note
Caching return values like this is also known asmemoization, which is why this Hook is calleduseMemo.
Usage
Skipping expensive recalculations
To cache a calculation between re-renders, wrap it in auseMemo call at the top level of your component:
import{useMemo}from'react';
functionTodoList({todos,tab,theme}){
constvisibleTodos =useMemo(() => filterTodos(todos, tab),[todos, tab]);
// ...
}You need to pass two things touseMemo:
- Acalculation function that takes no arguments, like
() =>, and returns what you wanted to calculate. - Alist of dependencies including every value within your component that’s used inside your calculation.
On the initial render, thevalue you’ll get fromuseMemo will be the result of calling yourcalculation.
On every subsequent render, React will compare thedependencies with the dependencies you passed during the last render. If none of the dependencies have changed (compared withObject.is),useMemo will return the value you already calculated before. Otherwise, React will re-run your calculation and return the new value.
In other words,useMemo caches a calculation result between re-renders until its dependencies change.
Let’s walk through an example to see when this is useful.
By default, React will re-run the entire body of your component every time that it re-renders. For example, if thisTodoList updates its state or receives new props from its parent, thefilterTodos function will re-run:
functionTodoList({todos,tab,theme}){
constvisibleTodos =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 expensive computation, you might want to skip doing it again if data hasn’t changed. If bothtodos andtab are the same as they were during the last render, wrapping the calculation inuseMemo like earlier lets you reusevisibleTodos you’ve already calculated before.
This type of caching is calledmemoization.
Note
You should only rely onuseMemo as a performance optimization. If your code doesn’t work without it, find the underlying problem and fix it first. Then you may adduseMemo to improve performance.
Deep Dive
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');
constvisibleTodos =filterTodos(todos,tab);
console.timeEnd('filter array');Perform the interaction you’re measuring (for example, typing into the input). You will then see logs likefilter 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 inuseMemo to verify whether the total logged time has decreased for that interaction or not:
console.time('filter array');
constvisibleTodos =useMemo(()=>{
returnfilterTodos(todos,tab);// Skipped if todos and tab haven't changed
},[todos,tab]);
console.timeEnd('filter array');useMemo won’t make thefirst 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 aCPU Throttling option for this.
Also note that measuring performance in development will not give you the most accurate results. (For example, whenStrict 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.
Deep Dive
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.
Optimizing withuseMemo is only valuable in a few cases:
- The calculation you’re putting in
useMemois noticeably slow, and its dependencies rarely change. - You pass it as a prop to a component wrapped in
memo. You want to skip re-rendering if the value hasn’t changed. Memoization lets your component re-render only when dependencies aren’t the same. - The value you’re passing is later used as a dependency of some Hook. For example, maybe another
useMemocalculation value depends on it. Or maybe you are depending on this value fromuseEffect.
There is no benefit to wrapping a calculation inuseMemo in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside of this approach is that code becomes less readable. Also, not all memoization is effective: a single value that’s “always new” is enough to break memoization for an entire component.
In practice, you can make a lot of memoization unnecessary by following a few principles:
- When a component visually wraps other components, let itaccept JSX as children. This way, when the wrapper component updates its own state, React knows that its children don’t need to re-render.
- Prefer local state and don’tlift state up any further than necessary. For example, don’t keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library.
- Keep yourrendering logic pure. If re-rendering a component causes a problem or produces some noticeable visual artifact, it’s a bug in your component! Fix the bug instead of adding memoization.
- Avoidunnecessary Effects that update state. Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over.
- Try toremove unnecessary dependencies from your Effects. For example, instead of memoization, it’s often simpler to move some object or a function inside an Effect or outside the component.
If a specific interaction still feels laggy,use the React Developer Tools profiler to see which components would benefit the most from memoization, and add memoization where needed. These principles make your components easier to debug and understand, so it’s good to follow them in any case. In the long term, we’re researchingdoing granular memoization automatically to solve this once and for all.
Example 1 of 2:Skipping recalculation withuseMemo
In this example, thefilterTodos implementation isartificially 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.
Switching the tabs feels slow because it forces the slowed downfilterTodos to re-execute. That’s expected because thetab has changed, and so the entire calculationneeds to re-run. (If you’re curious why it runs twice, it’s explainedhere.)
Toggle the theme.Thanks touseMemo, it’s fast despite the artificial slowdown! The slowfilterTodos call was skipped because bothtodos andtab (which you pass as dependencies touseMemo) haven’t changed since the last render.
import{useMemo}from'react';import{filterTodos}from'./utils.js'exportdefaultfunctionTodoList({todos,theme,tab}){constvisibleTodos =useMemo(()=>filterTodos(todos,tab),[todos,tab]);return(<divclassName={theme}><p><b>Note:<code>filterTodos</code> is artificially slowed down!</b></p><ul>{visibleTodos.map(todo=>(<likey={todo.id}>{todo.completed ?<s>{todo.text}</s> :todo.text}</li>))}</ul></div>);}
Skipping re-rendering of components
In some cases,useMemo can also help you optimize performance of re-rendering child components. To illustrate this, let’s say thisTodoList component passes thevisibleTodos as a prop to the childList component:
exportdefaultfunctionTodoList({todos,tab,theme}){
// ...
return(
<divclassName={theme}>
<Listitems={visibleTodos}/>
</div>
);
}You’ve noticed that toggling thetheme prop freezes the app for a moment, but if you remove<List /> from your JSX, it feels fast. This tells you that it’s worth trying to optimize theList component.
By default, when a component re-renders, React re-renders all of its children recursively. This is why, whenTodoList re-renders with a differenttheme, theList componentalso re-renders. This is fine for components that don’t require much calculation to re-render. But if you’ve verified that a re-render is slow, you can tellList to skip re-rendering when its props are the same as on last render by wrapping it inmemo:
import{memo}from'react';
constList =memo(functionList({items}){
// ...
});With this change,List will skip re-rendering if all of its props are thesame as on the last render. This is where caching the calculation becomes important! Imagine that you calculatedvisibleTodos withoutuseMemo:
exportdefaultfunctionTodoList({todos,tab,theme}){
// Every time the theme changes, this will be a different array...
constvisibleTodos =filterTodos(todos,tab);
return(
<divclassName={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<Listitems={visibleTodos}/>
</div>
);
}In the above example, thefilterTodos function always creates adifferent array, similar to how the{} object literal always creates a new object. Normally, this wouldn’t be a problem, but it means thatList props will never be the same, and yourmemo optimization won’t work. This is whereuseMemo comes in handy:
exportdefaultfunctionTodoList({todos,tab,theme}){
// Tell React to cache your calculation between re-renders...
constvisibleTodos =useMemo(
()=>filterTodos(todos,tab),
[todos,tab]// ...so as long as these dependencies don't change...
);
return(
<divclassName={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<Listitems={visibleTodos}/>
</div>
);
}By wrapping thevisibleTodos calculation inuseMemo, you ensure that it has thesame value between the re-renders (until dependencies change). You don’thave to wrap a calculation inuseMemo unless you do it for some specific reason. In this example, the reason is that you pass it to a component wrapped inmemo, and this lets it skip re-rendering. There are a few other reasons to adduseMemo which are described further on this page.
Deep Dive
Instead of wrappingList inmemo, you could wrap the<List /> JSX node itself inuseMemo:
exportdefaultfunctionTodoList({todos,tab,theme}){
constvisibleTodos =useMemo(()=>filterTodos(todos,tab),[todos,tab]);
constchildren =useMemo(()=><Listitems={visibleTodos}/>,[visibleTodos]);
return(
<divclassName={theme}>
{children}
</div>
);
}The behavior would be the same. If thevisibleTodos haven’t changed,List won’t be re-rendered.
A JSX node like<List items={visibleTodos} /> is an object like{ type: List, props: { items: visibleTodos } }. Creating this object is very cheap, but React doesn’t know whether its contents is the same as last time or not. This is why by default, React will re-render theList component.
However, if React sees the same exact JSX as during the previous render, it won’t try to re-render your component. This is because JSX nodes areimmutable. A JSX node object could not have changed over time, so React knows it’s safe to skip a re-render. However, for this to work, the node has toactually be the same object, not merely look the same in code. This is whatuseMemo does in this example.
Manually wrapping JSX nodes intouseMemo is not convenient. For example, you can’t do this conditionally. This is usually why you would wrap components withmemo instead of wrapping JSX nodes.
Example 1 of 2:Skipping re-rendering withuseMemo andmemo
In this example, theList component isartificially 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.
Switching the tabs feels slow because it forces the slowed downList to re-render. That’s expected because thetab has changed, and so you need to reflect the user’s new choice on the screen.
Next, try toggling the theme.Thanks touseMemo together withmemo, it’s fast despite the artificial slowdown! TheList skipped re-rendering because thevisibleTodos array has not changed since the last render. ThevisibleTodos array has not changed because bothtodos andtab (which you pass as dependencies touseMemo) haven’t changed since the last render.
import{useMemo}from'react';importListfrom'./List.js';import{filterTodos}from'./utils.js'exportdefaultfunctionTodoList({todos,theme,tab}){constvisibleTodos =useMemo(()=>filterTodos(todos,tab),[todos,tab]);return(<divclassName={theme}><p><b>Note:<code>List</code> is artificially slowed down!</b></p><Listitems={visibleTodos}/></div>);}
Preventing an Effect from firing too often
Sometimes, you might want to use a value inside anEffect:
functionChatRoom({roomId}){
const[message,setMessage] =useState('');
constoptions ={
serverUrl:'https://localhost:1234',
roomId:roomId
}
useEffect(()=>{
constconnection =createConnection(options);
connection.connect();
// ...This creates a problem.Every reactive value must be declared as a dependency of your Effect. However, if you declareoptions as a dependency, it will cause your Effect to constantly reconnect to the chat room:
useEffect(()=>{
constconnection =createConnection(options);
connection.connect();
return()=>connection.disconnect();
},[options]);// 🔴 Problem: This dependency changes on every render
// ...To solve this, you can wrap the object you need to call from an Effect inuseMemo:
functionChatRoom({roomId}){
const[message,setMessage] =useState('');
constoptions =useMemo(()=>{
return{
serverUrl:'https://localhost:1234',
roomId:roomId
};
},[roomId]);// ✅ Only changes when roomId changes
useEffect(()=>{
constconnection =createConnection(options);
connection.connect();
return()=>connection.disconnect();
},[options]);// ✅ Only changes when options changes
// ...This ensures that theoptions object is the same between re-renders ifuseMemo returns the cached object.
However, sinceuseMemo is performance optimization, not a semantic guarantee, React may throw away the cached value ifthere is a specific reason to do that. This will also cause the effect to re-fire,so it’s even better to remove the need for a function dependency by moving your objectinside the Effect:
functionChatRoom({roomId}){
const[message,setMessage] =useState('');
useEffect(()=>{
constoptions ={// ✅ No need for useMemo or object dependencies!
serverUrl:'https://localhost:1234',
roomId:roomId
}
constconnection =createConnection(options);
connection.connect();
return()=>connection.disconnect();
},[roomId]);// ✅ Only changes when roomId changes
// ...Now your code is simpler and doesn’t needuseMemo.Learn more about removing Effect dependencies.
Memoizing a dependency of another Hook
Suppose you have a calculation that depends on an object created directly in the component body:
functionDropdown({allItems,text}){
constsearchOptions ={matchMode:'whole-word',text};
constvisibleItems =useMemo(()=>{
returnsearchItems(allItems,searchOptions);
},[allItems,searchOptions]);// 🚩 Caution: Dependency on an object created in the component body
// ...Depending on an object like this defeats the point of memoization. When a component re-renders, all of the code directly inside the component body runs again.The lines of code creating thesearchOptions object will also run on every re-render. SincesearchOptions is a dependency of youruseMemo call, and it’s different every time, React knows the dependencies are different, and recalculatesearchItems every time.
To fix this, you could memoize thesearchOptions objectitself before passing it as a dependency:
functionDropdown({allItems,text}){
constsearchOptions =useMemo(()=>{
return{matchMode:'whole-word',text};
},[text]);// ✅ Only changes when text changes
constvisibleItems =useMemo(()=>{
returnsearchItems(allItems,searchOptions);
},[allItems,searchOptions]);// ✅ Only changes when allItems or searchOptions changes
// ...In the example above, if thetext did not change, thesearchOptions object also won’t change. However, an even better fix is to move thesearchOptions object declarationinside of theuseMemo calculation function:
functionDropdown({allItems,text}){
constvisibleItems =useMemo(()=>{
constsearchOptions ={matchMode:'whole-word',text};
returnsearchItems(allItems,searchOptions);
},[allItems,text]);// ✅ Only changes when allItems or text changes
// ...Now your calculation depends ontext directly (which is a string and can’t “accidentally” become different).
Memoizing a function
Suppose theForm component is wrapped inmemo. You want to pass a function to it as a prop:
exportdefaultfunctionProductPage({productId,referrer}){
functionhandleSubmit(orderDetails){
post('/product/' +productId +'/buy',{
referrer,
orderDetails
});
}
return<FormonSubmit={handleSubmit}/>;
}Just as{} creates a different object, function declarations likefunction() {} and expressions like() => {} produce adifferent function on every re-render. By itself, creating a new function is not a problem. This is not something to avoid! However, if theForm component is memoized, presumably you want to skip re-rendering it when no props have changed. A prop that isalways different would defeat the point of memoization.
To memoize a function withuseMemo, your calculation function would have to return another function:
exportdefaultfunctionPage({productId,referrer}){
consthandleSubmit =useMemo(()=>{
return(orderDetails)=>{
post('/product/' +productId +'/buy',{
referrer,
orderDetails
});
};
},[productId,referrer]);
return<FormonSubmit={handleSubmit}/>;
}This looks clunky!Memoizing functions is common enough that React has a built-in Hook specifically for that. Wrap your functions intouseCallback instead ofuseMemo to avoid having to write an extra nested function:
exportdefaultfunctionPage({productId,referrer}){
consthandleSubmit =useCallback((orderDetails)=>{
post('/product/' +productId +'/buy',{
referrer,
orderDetails
});
},[productId,referrer]);
return<FormonSubmit={handleSubmit}/>;
}The two examples above are completely equivalent. The only benefit touseCallback is that it lets you avoid writing an extra nested function inside. It doesn’t do anything else.Read more aboutuseCallback.
Troubleshooting
My calculation runs twice on every re-render
InStrict Mode, React will call some of your functions twice instead of once:
functionTodoList({todos,tab}){
// This component function will run twice for every render.
constvisibleTodos =useMemo(()=>{
// This calculation will run twice if any of the dependencies change.
returnfilterTodos(todos,tab);
},[todos,tab]);
// ...This is expected and shouldn’t break your code.
Thisdevelopment-only behavior helps youkeep 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 and fix the mistake.
For example, this impure calculation function mutates an array you received as a prop:
constvisibleTodos =useMemo(()=>{
// 🚩 Mistake: mutating a prop
todos.push({id:'last',text:'Go for a walk!'});
constfiltered =filterTodos(todos,tab);
returnfiltered;
},[todos,tab]);React calls your function twice, so you’d notice the todo is added twice. Your calculation shouldn’t change any existing objects, but it’s okay to change anynew objects you created during the calculation. For example, if thefilterTodos function always returns adifferent array, you can mutatethat array instead:
constvisibleTodos =useMemo(()=>{
constfiltered =filterTodos(todos,tab);
// ✅ Correct: mutating an object you created during the calculation
filtered.push({id:'last',text:'Go for a walk!'});
returnfiltered;
},[todos,tab]);Readkeeping components pure to learn more about purity.
Also, check out the guides onupdating objects andupdating arrays without mutation.
MyuseMemo call is supposed to return an object, but returns undefined
This code doesn’t work:
// 🔴 You can't return an object from an arrow function with () => {
constsearchOptions =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 mistakes. You could fix it by adding parentheses like({ and}):
// This works, but is easy for someone to break again
constsearchOptions =useMemo(()=>({
matchMode:'whole-word',
text:text
}),[text]);However, this is still confusing and too easy for someone to break by removing the parentheses.
To avoid this mistake, write areturn statement explicitly:
// ✅ This works and is explicit
constsearchOptions =useMemo(()=>{
return{
matchMode:'whole-word',
text:text
};
},[text]);Every time my component renders, the calculation inuseMemo 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:
functionTodoList({todos,tab}){
// 🔴 Recalculates every time: no dependency array
constvisibleTodos =useMemo(()=>filterTodos(todos,tab));
// ...This is the corrected version passing the dependency array as a second argument:
functionTodoList({todos,tab}){
// ✅ Does not recalculate unnecessarily
constvisibleTodos =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:
constvisibleTodos =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 astemp1 and the second one got saved astemp2, 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]);// ... and so on for every dependency ...When you find which dependency breaks memoization, either find a way to remove it, ormemoize it as well.
I need to calluseMemo for each list item in a loop, but it’s not allowed
Suppose theChart component is wrapped inmemo. You want to skip re-rendering everyChart in the list when theReportList component re-renders. However, you can’t calluseMemo in a loop:
functionReportList({items}){
return(
<article>
{items.map(item=>{
// 🔴 You can't call useMemo in a loop like this:
constdata =useMemo(()=>calculateReport(item),[item]);
return(
<figurekey={item.id}>
<Chartdata={data}/>
</figure>
);
})}
</article>
);
}Instead, extract a component for each item and memoize data for individual items:
functionReportList({items}){
return(
<article>
{items.map(item=>
<Reportkey={item.id}item={item}/>
)}
</article>
);
}
functionReport({item}){
// ✅ Call useMemo at the top level:
constdata =useMemo(()=>calculateReport(item),[item]);
return(
<figure>
<Chartdata={data}/>
</figure>
);
}Alternatively, you could removeuseMemo and instead wrapReport itself inmemo. If theitem prop does not change,Report will skip re-rendering, soChart will skip re-rendering too:
functionReportList({items}){
// ...
}
constReport =memo(functionReport({item}){
constdata =calculateReport(item);
return(
<figure>
<Chartdata={data}/>
</figure>
);
});