Without memoization, a value computed directly in a component's function body gets recalculated from scratch on every single render, even if the inputs it depends on haven't changed at all — useMemo wraps that computation in a function, and only re-runs it when one of the values in the dependency array has actually changed since the last render, returning the previously cached result otherwise. This is specifically useful for computations that are genuinely expensive, like processing a large array, or for producing a stable object/array reference that needs to avoid changing identity on every render, such as when it's passed as a prop to a child wrapped in React.memo().
1Understanding useMemo
Without memoization, a value computed directly in a component's function body gets recalculated from scratch on every single render, even if the inputs it depends on haven't changed at all — useMemo wraps that computation in a function, and only re-runs it when one of the values in the dependency array has actually changed since the last render, returning the previously cached result otherwise. This is specifically useful for computations that are genuinely expensive, like processing a large array, or for producing a stable object/array reference that needs to avoid changing identity on every render, such as when it's passed as a prop to a child wrapped in React.memo().
Don't reach for useMemo by default on every computed value — it adds its own small overhead and complexity, and is only actually worth it for computations that are either genuinely expensive or need a stable reference for something like a memoized child component's props.
import { useMemo } from 'react';
function ProductList({ products, filter }) {
const filtered = useMemo(() => {
console.log('Filtering products...');
return products.filter(p => p.category === filter);
}, [products, filter]);
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}2Practical Example
Here is a real-world application of useMemo showing how it is used in production React code.
import { useMemo } from 'react';
function Cart({ items }) {
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <p>Total: ${total}</p>;
}
// items = [{ price: 10 }, { price: 25 }, { price: 5 }]3Best Practices
Follow these guidelines when working with useMemo:
1. Reserve useMemo for computations that are either measurably expensive or need to maintain a stable object/array reference between renders, not for cheap calculations
2. Include every value the memoized computation actually depends on in the dependency array, exactly like useEffect's dependency array rules
3. Pair useMemo-produced stable references with a React.memo()-wrapped child component when the goal is specifically avoiding that child's unnecessary re-renders
Tip: Don't reach for useMemo by default on every computed value — it adds its own small overhead and complexity, and is only actually worth it for computations that are either genuinely expensive or need a stable reference for something like a memoized child component's props.
import { useMemo } from 'react';
function ProductList({ products, filter }) {
const filtered = useMemo(() => {
console.log('Filtering products...');
return products.filter(p => p.category === filter);
}, [products, filter]);
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}4Stabilizing Props for React.memo Children
React.memo skips re-rendering a child when its props are shallowly equal to last time. But an object or array literal built inline as a prop is a new reference every render, which silently defeats React.memo. Wrapping that value in useMemo gives the memoized child a stable reference, so it actually skips re-rendering when the underlying data hasn't changed.
// Defeats React.memo: new array reference every render
<List items={data.filter(d => d.active)} />
// Stable reference: React.memo can actually skip re-rendering
const active = useMemo(() => data.filter(d => d.active), [data]);
<List items={active} />5useMemo Is Not a Guarantee
useMemo is a performance hint, not a correctness contract — React is allowed to discard a cached value and recompute it in certain situations (memory pressure, components kept offscreen). Code should stay correct even on a render where the memoization didn't actually apply; never rely on useMemo purely to skip running a computation for correctness reasons.
// Wrong mental model: 'this only ever runs once'
// Correct mental model: 'this is cached AS AN OPTIMIZATION'
const value = useMemo(() => compute(a, b), [a, b]);