useMemo: Memoizing Values
useMemo is a performance optimization hook that caches the result of a computation between renders. Instead of re-running an expensive calculation on every render, React returns the cached value and only recomputes when the specified dependencies change.
The Signature
const memoizedValue = useMemo( () => expensiveComputation(a, b), // factory function [a, b] // dependency array )
On the first render, the factory function runs and its result is cached
On subsequent renders, if
aandbare the same, React returns the cached value without calling the factoryWhen
aorbchanges, the factory runs again and the new result is cached
When Every Render Is Expensive: Filtering a Large List
Without useMemo, this filter runs on every re-render — even when the parent component re-renders for an unrelated reason (like a theme toggle).
import { useState, useMemo } from 'react'
// Simulated large dataset
function generateItems(count) {
return Array.from({ length: count }, (_, i) => ({
id: i,
name: `Item ${i}`,
price: Math.random() * 100,
inStock: Math.random() > 0.3,
}))
}
const allItems = generateItems(10_000)
// ❌ Without useMemo — filter runs on every render
function ProductList({ minPrice }) {
const [theme, setTheme] = useState('light')
// This runs every time 'theme' changes, even though minPrice didn't change
const filtered = allItems.filter(item => item.price >= minPrice && item.inStock)
return (
<div className={theme}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
<ul>{filtered.map(item => <li key={item.id}>{item.name} — ${item.price.toFixed(2)}</li>)}</ul>
</div>
)
}
// ✅ With useMemo — filter only runs when minPrice changes
function ProductList({ minPrice }) {
const [theme, setTheme] = useState('light')
const filtered = useMemo(
() => allItems.filter(item => item.price >= minPrice && item.inStock),
[minPrice] // only recompute when minPrice changes
)
return (
<div className={theme}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
<ul>{filtered.map(item => <li key={item.id}>{item.name} — ${item.price.toFixed(2)}</li>)}</ul>
</div>
)
}Toggling the theme re-renders ProductList, but filtered is returned from the cache — the expensive filter over 10,000 items does not run.
Referential Stability for React.memo Children
useMemo is not just about expensive calculations. It is equally important for referential stability — ensuring objects and arrays maintain the same reference between renders so that memoized children do not re-render unnecessarily.
import { useState, useMemo, memo } from 'react'
// A chart that is expensive to render
const Chart = memo(function Chart({ data }) {
console.log('Chart re-rendered')
return <svg>{/* expensive rendering */}</svg>
})
// ❌ Without useMemo — Chart re-renders on every keystroke
function Dashboard({ userId }) {
const [filter, setFilter] = useState('')
// New object reference on every render → Chart sees "new" data → re-renders
const chartData = {
userId,
series: computeSeriesData(userId), // expensive
}
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<Chart data={chartData} />
</div>
)
}
// ✅ With useMemo — Chart only re-renders when userId changes
function Dashboard({ userId }) {
const [filter, setFilter] = useState('')
// Stable reference — same object as long as userId is the same
const chartData = useMemo(
() => ({
userId,
series: computeSeriesData(userId),
}),
[userId]
)
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<Chart data={chartData} />
</div>
)
}Deriving Complex Data from Multiple Inputs
import { useMemo } from 'react'
function SalesReport({ transactions, startDate, endDate, category }) {
// Computed from four different inputs — recompute only when any changes
const summary = useMemo(() => {
const filtered = transactions.filter(tx => {
const d = new Date(tx.date)
return (
d >= startDate &&
d <= endDate &&
(category === 'all' || tx.category === category)
)
})
const total = filtered.reduce((sum, tx) => sum + tx.amount, 0)
const byCategory = filtered.reduce((acc, tx) => {
acc[tx.category] = (acc[tx.category] || 0) + tx.amount
return acc
}, {})
return { filtered, total, byCategory }
}, [transactions, startDate, endDate, category])
return (
<div>
<p>Total: ${summary.total.toFixed(2)}</p>
<p>Transactions: {summary.filtered.length}</p>
</div>
)
}When NOT to Use useMemo
useMemo has a cost: React must allocate memory for the cached value, store the dependencies, and compare them on every render. For simple computations, this overhead exceeds the savings.
Simple arithmetic —
const total = price * quantitydoes not need memoizationString concatenation —
const fullName =${first} ${last}`` is fast enoughComponents that rarely re-render — if the parent barely re-renders, the savings are minimal
First render —
useMemoonly saves on subsequent renders; the first render always runs the factory
How to Measure if useMemo Helps
Use the React DevTools Profiler:
Open React DevTools → Profiler tab
Click "Record" and interact with your app
Stop recording and look at the flame chart
Components shown in orange/red are expensive
Check "Why did this render?" to see if props changed
Add
useMemoto stabilize props that are unnecessarily re-createdRecord again to confirm the improvement
Scenario | Use useMemo? |
|---|---|
Filter/sort/transform a list of 10,000+ items | Yes — likely worth it |
Build an object passed to React.memo child | Yes — ensures referential stability |
Derive complex data from multiple expensive inputs | Yes — profile first |
| No — trivially fast |
| No — no real cost |
Function that is passed to a child | Use |