useMemo Hook
useMemo is a React hook that memoizes the result of a calculation between renders, so React only recomputes it when its dependencies change. It exists purely as a performance optimization: it lets you skip expensive work on renders where the inputs to that work haven’t changed. It does not change what your component renders — only how much work React does to get there.
Overview / How it works
Every time a component re-renders (because its state changed, its props changed, or a parent re-rendered), the entire function body runs again from top to bottom. Any variable declared with a plain const is recalculated on every single render, even if the inputs to that calculation haven’t changed at all. Most of the time this is fine — computing const doubled = count * 2 costs nothing. But some calculations are genuinely expensive: filtering or sorting a large array, running a heavy string-processing routine, building a derived data structure from thousands of items. Recomputing those on every render, including renders triggered by something totally unrelated (like a sibling input’s value changing), wastes CPU time and can make the UI feel sluggish.
useMemo solves this by caching the return value of a function across renders. You give it a “create” function and a dependency array. On the first render, React calls the function and stores the result. On every subsequent render, React compares the new dependency array to the previous one (using Object.is comparison on each item). If none of the dependencies changed, React skips calling your function and simply returns the cached value from last time. If any dependency changed, React reruns the function and caches the new result.
It’s important to understand what useMemo is not: it is not a guarantee. The React documentation is explicit that memoization is a performance hint, not a semantic contract — in rare cases (such as with certain future concurrent rendering features) React may discard a cached value and recompute it anyway. Never rely on useMemo for correctness (for example, don’t use it to avoid running side effects — that’s what useEffect is for). Use it only to reduce redundant work that doesn’t matter if it occasionally reruns.
useMemo also matters for referential equality. In JavaScript, two separately-created objects or arrays are never === equal even if their contents are identical: {} === {} is false. Since a component body reruns on every render, an object literal or array literal created inside it is a brand-new reference each time. This becomes a problem when that value is passed as a prop to a child wrapped in React.memo, or used as a dependency of another hook like useEffect — the child re-renders, or the effect re-runs, every single time, even though the data “didn’t really change.” Wrapping the object or array creation in useMemo keeps the same reference across renders as long as the underlying inputs are unchanged, which lets memoized children and effects correctly skip unnecessary work.
Syntax
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
| Part | Description |
|---|---|
() => computeExpensiveValue(a, b) |
The “create” function. React calls it to produce the value. Do not call it yourself — pass the function itself, not its result. |
[a, b] |
The dependency array. Every reactive value (state, props, or other variables from the component body) read inside the create function must be listed here. |
memoizedValue |
The cached return value. On renders where the dependencies haven’t changed, this is the exact same value as the previous render. |
The create function must be pure — no state updates, no side effects, no subscriptions inside it. It should only compute and return a value.
Examples
Example 1: Memoizing an expensive filter/sort
import { useState, useMemo } from "react";
function ProductList({ products }) {
const [query, setQuery] = useState("");
const [darkMode, setDarkMode] = useState(false);
const filteredProducts = useMemo(() => {
console.log("Filtering products...");
return products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase())
);
}, [products, query]);
return (
<div className={darkMode ? "app dark" : "app"}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products"
/>
<button onClick={() => setDarkMode(!darkMode)}>
Toggle theme
</button>
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}
export default ProductList;
Renders: a search input, a theme-toggle button, and a filtered list of product names.
Output: Filtering products...
Without useMemo, clicking “Toggle theme” would re-render ProductList and rerun the filter on the full products array, even though neither products nor query changed. With useMemo, the dependency array [products, query] is unchanged on that render, so React skips the filter function entirely and reuses the cached array. The console log only prints when query or products actually changes, not when darkMode toggles.
Example 2: Stabilizing a reference for a memoized child
import { useState, useMemo } from "react";
function SearchPanel({ items }) {
const [term, setTerm] = useState("");
const [count, setCount] = useState(0);
const options = useMemo(
() => ({ caseSensitive: false, term }),
[term]
);
return (
<div>
<input value={term} onChange={(e) => setTerm(e.target.value)} />
<button onClick={() => setCount(count + 1)}>Clicked {count}</button>
<ResultsList items={items} options={options} />
</div>
);
}
export default SearchPanel;
Renders: a text input, a counter button, and a child ResultsList component that receives an options object as a prop.
If ResultsList is wrapped in React.memo, it will only re-render when its props actually change. Without useMemo, the options object literal would be recreated on every render of SearchPanel — including when only count changes — defeating React.memo entirely, since a new object reference always compares as “different.” By memoizing options with [term] as the dependency, clicking the counter button leaves options referentially identical, so the memoized ResultsList skips re-rendering.
Example 3: Memoizing a derived computation with multiple dependencies
import { useState, useMemo } from "react";
function Cart({ items }) {
const [taxRate, setTaxRate] = useState(0.08);
const [discountCode, setDiscountCode] = useState("");
const total = useMemo(() => {
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
const discount = discountCode === "SAVE10" ? subtotal * 0.1 : 0;
return (subtotal - discount) * (1 + taxRate);
}, [items, taxRate, discountCode]);
return (
<div>
<p>Total: ${total.toFixed(2)}</p>
<input
value={discountCode}
onChange={(e) => setDiscountCode(e.target.value)}
placeholder="Discount code"
/>
</div>
);
}
export default Cart;
Renders: a paragraph showing the computed total price, and an input for a discount code.
The total recalculates only when items, taxRate, or discountCode changes. This shows that useMemo can depend on multiple reactive values at once — React reruns the create function if any of the listed dependencies changed since the last render.
How it works step by step
- On mount: React calls the create function, stores its return value alongside the current dependency array, and uses that value for the render.
- On update: React compares each item in the new dependency array to the corresponding item from the previous render using
Object.is. If all are equal, React returns the previously cached value without calling the create function again. If any differ, React calls the create function again and updates the cache with the new value and new dependencies. - On unmount: the cached value is discarded along with the rest of the component’s hook state; there is nothing to clean up since the create function must be a pure, synchronous computation.
Common Mistakes
Mistake 1: Omitting a dependency that the calculation actually uses
// Wrong: `taxRate` is used inside but missing from the dependency array
const total = useMemo(() => {
return subtotal * (1 + taxRate);
}, [subtotal]);
This is wrong because when taxRate changes, React still thinks nothing relevant changed and keeps returning the stale cached total — the UI silently shows an outdated price.
// Correct: list every reactive value read inside the function
const total = useMemo(() => {
return subtotal * (1 + taxRate);
}, [subtotal, taxRate]);
Mistake 2: Using useMemo for cheap calculations “just in case”
// Wrong: wrapping trivial arithmetic adds overhead for no benefit
const doubled = useMemo(() => count * 2, [count]);
Memoization itself has a cost — storing the dependency array, comparing it each render. For simple, fast operations like this, that bookkeeping costs more than just recalculating the value plainly. Reserve useMemo for calculations that are measurably expensive (loops over large data, heavy string/number processing) or for stabilizing object/array references passed to memoized children.
// Correct: just compute it directly
const doubled = count * 2;
Mistake 3: Treating useMemo as a guarantee for side effects
Because React may in rare cases discard a memoized value and recompute it, never put side effects (network calls, subscriptions, mutations of external state) inside a useMemo create function. Side effects belong in useEffect, where React’s timing guarantees actually apply.
Best Practices
- Only reach for
useMemoafter noticing an actual performance problem (e.g. via the React DevTools Profiler) — don’t memoize everything by default. - List every value from the component body that the create function reads in the dependency array; let your linter’s
react-hooks/exhaustive-depsrule catch omissions. - Keep the create function pure: no state updates, no mutations, no async code inside it.
- Use
useMemoto stabilize object/array references passed as props to children wrapped inReact.memo, or used as dependencies of other hooks. - Don’t use
useMemoto memoize primitive values from cheap arithmetic — the overhead usually isn’t worth it. - Pair
useMemo(memoizing values) withuseCallback(memoizing functions) when a component passes callbacks to memoized children.
Practice Exercises
- Write a component that renders a list of 10,000 numbers and computes their sum using
useMemo, with a dependency on the array. Add an unrelated state toggle (like a dark-mode switch) and useconsole.loginside the create function to confirm the sum is only recalculated when the array changes. - Build a
UserDirectorycomponent that filters a list of users by a search term usinguseMemo. Intentionally leave the search term out of the dependency array, observe the stale results bug, then fix it. - Create a parent component that passes a memoized options object to a child wrapped in
React.memo. Add a button in the parent that updates unrelated state, and verify (with a console log in the child) that the child does not re-render when the memoized object’s dependencies haven’t changed.
Summary
useMemocaches the result of a calculation across renders and only recomputes it when its dependencies change.- It’s a performance optimization, not a correctness guarantee — never rely on it for side effects.
- It’s most valuable for genuinely expensive computations and for keeping object/array references stable for memoized children or hook dependencies.
- Every reactive value used inside the create function must be listed in the dependency array to avoid stale results.
- Don’t overuse it — wrapping cheap calculations in
useMemocan add more overhead than it saves.
