Introduction to useMemo
The useMemo Hook lets you cache the result of a calculation between renders. React can reuse the previously calculated value when the dependencies have not changed, avoiding unnecessary recalculation.
This is particularly useful when a component performs expensive operations, such as filtering a large collection, sorting complex data, or calculating derived values. However, useMemo is a performance optimization, not a requirement for correct application behavior.
Basic Syntax
import { useMemo } from 'react';
const cachedValue = useMemo(() => {
return calculateValue(data);
}, [data]);
The Hook accepts a calculation function and a dependency array. React runs the calculation during rendering and can reuse its result on later renders while the dependencies remain unchanged.
- Calculation function: Returns the value you want to cache.
- Dependencies: Values that determine whether the calculation needs to run again.
- Cached value: The result returned by
useMemo.
How useMemo Works
React compares each dependency with its previous value using Object.is. If every dependency is unchanged, React can reuse the cached result. If a dependency changes, React runs the calculation again.
import { useMemo, useState } from 'react';
function ProductTotal() {
const [price, setPrice] = useState(100);
const [quantity, setQuantity] = useState(2);
const total = useMemo(() => {
console.log('Calculating total');
return price * quantity;
}, [price, quantity]);
return (
<div>
<p>Total: {total}</p>
<button onClick={() => setPrice(price + 10)}>
Increase price
</button>
<button onClick={() => setQuantity(quantity + 1)}>
Increase quantity
</button>
</div>
);
}
export default ProductTotal;
The total is recalculated when either the price or quantity changes. Other renders with both values unchanged can reuse the cached result. Because multiplication is inexpensive, this example illustrates the API rather than demonstrating a meaningful performance improvement.
Example: Filtering a Large List
Filtering or sorting a large dataset may take noticeable time. If a component also re-renders for unrelated reasons, memoization can avoid repeating the same calculation when its inputs have not changed.
import { useMemo, useState } from 'react';
const products = [
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Keyboard', price: 80 },
{ id: 3, name: 'Monitor', price: 350 },
{ id: 4, name: 'Mouse', price: 40 }
];
function ProductList() {
const [search, setSearch] = useState('');
const [showAffordable, setShowAffordable] = useState(false);
const filteredProducts = useMemo(() => {
const normalizedSearch = search.toLowerCase();
return products.filter((product) => {
const matchesSearch = product.name
.toLowerCase()
.includes(normalizedSearch);
const matchesPrice =
!showAffordable || product.price <= 100;
return matchesSearch && matchesPrice;
});
}, [search, showAffordable]);
return (
<div>
<input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="Search products"
/>
<label>
<input
type="checkbox"
checked={showAffordable}
onChange={(event) =>
setShowAffordable(event.target.checked)
}
/>
Show products under $100
</label>
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
}
export default ProductList;
The filtered array is recalculated when the search term or price filter changes. If the component re-renders for another reason while these dependencies remain unchanged, React can reuse the cached array.
For smaller collections, a direct filter() call may be simpler and fast enough. Measure performance before introducing memoization.
useMemo and Object Dependencies
Objects and arrays are compared by reference, not by their contents. Creating a new object during every render gives it a new reference, which can cause a memoized calculation to run again.
Consider this example:
const options = { category, sortOrder };
const results = useMemo(() => {
return filterProducts(products, options);
}, [products, options]);
Since options is recreated on every render, its reference changes and the memoization may provide no benefit.
A simpler solution is to create the object inside the calculation and depend on its primitive inputs:
const results = useMemo(() => {
const options = { category, sortOrder };
return filterProducts(products, options);
}, [products, category, sortOrder]);
Now the calculation depends on the actual values used to produce the result. This often avoids the need to memoize the options object separately.
useMemo vs useCallback
These Hooks both support memoization, but they cache different things. useMemo caches a calculated result, whereas useCallback caches a function reference.
| Feature | useMemo | useCallback |
|---|---|---|
| Caches | A calculated value | A function reference |
| Typical use | Expensive calculations and derived data | Stable callbacks passed to memoized children |
| Returns | The calculated result | The cached function |
import { useCallback, useMemo } from 'react';
const total = useMemo(() => {
return calculateTotal(items);
}, [items]);
const handleSelect = useCallback((id) => {
selectItem(id);
}, [selectItem]);
Learn more about React Hooks and explore callback memoization in the related useCallback documentation if that topic is available in your documentation set.
Using useMemo with React.memo
React.memo can skip rendering a child component when its props have not changed. However, newly created arrays, objects, or functions passed as props can defeat this optimization because their references change between renders.
import { memo, useMemo, useState } from 'react';
const ProductSummary = memo(function ProductSummary({ items }) {
console.log('ProductSummary rendered');
return <p>Products: {items.length}</p>;
});
function Dashboard({ products }) {
const [theme, setTheme] = useState('light');
const availableProducts = useMemo(() => {
return products.filter((product) => product.available);
}, [products]);
return (
<div className={theme}>
<button
onClick={() =>
setTheme((current) =>
current === 'light' ? 'dark' : 'light'
)
}
>
Toggle theme
</button>
<ProductSummary items={availableProducts} />
</div>
);
}
If the parent re-renders because the theme changes while products remains the same reference, the memoized array can remain stable. This allows ProductSummary to skip rendering when its props are unchanged. The optimization is useful only when avoiding the child render is worthwhile.
When Should You Avoid useMemo?
Memoization introduces complexity, so it should be applied intentionally rather than to every expression.
- Simple calculations: Basic arithmetic and small transformations usually do not need memoization.
- Unstable dependencies: If dependencies change on every render, the cached value is unlikely to be reused.
- Incorrect dependency lists: Missing dependencies can produce stale calculations. Include every reactive value used by the calculation.
- Side effects: Do not perform network requests, update state, or mutate external systems inside the calculation. It should be pure.
- Correctness requirements: Do not rely on the cache as permanent storage. React may discard memoized values in certain situations.
If your component is slow, use the React Developer Tools Profiler to identify the expensive work before optimizing it. For state transitions, see useReducer; for external synchronization, see useEffect.
Dependency Array Rules
A correct dependency array is essential for predictable behavior. Include all props, state values, and variables declared in the component that the calculation reads.
| Pattern | Behavior |
|---|---|
useMemo(fn, [value]) |
Recalculates when value changes. |
useMemo(fn, [a, b]) |
Recalculates when either dependency changes. |
useMemo(fn, []) |
Has no reactive dependencies; the result can be reused across ordinary re-renders. |
React expects the dependency list to have a constant number of entries and to be written inline. In development Strict Mode, React may call the calculation more than once to help identify accidental impurities.
Best Practices
- Start with clear, straightforward component code and optimize only when there is a measurable benefit.
- Keep memoized calculations pure and free of side effects.
- Specify every reactive dependency used by the calculation.
- Prefer primitive dependencies when they express the calculation's actual inputs.
- Use
useMemofor expensive calculations or when a stable object or array reference is needed by an optimization. - Remember that
useMemodoes not prevent the component itself from rendering. - Consider whether React's compiler is enabled in your project; supported compiler optimizations may reduce the need for manual memoization.
Key Takeaway
useMemo caches a calculation result between renders while its dependencies remain unchanged. It can improve performance for expensive computations and help preserve object or array references, but it should not replace correct state management or be used indiscriminately.