Memoize expensive work
Let unrelated renders skip work with useMemo, useCallback and memo.
From frontend-patterns, “Performance Optimization: Memoization”.
500 cards, unrelated counter
Click +1, then Update one market. Rendered cards flash.
count = 0
memo, inline handlerBad
memo + useCallbackGood
onSelect={handleSelect}
const handleSelect = useCallback((id: string) => setSelectedId(id), [])
export const MarketCard = React.memo<MarketCardProps>(({ market, onSelect }) => (
<button className="market-card" onClick={() => onSelect(market.id)}>
<h3>{market.name}</h3>
<p>{market.volume}</p>
</button>
))
{markets.map((m) => <MarketCard key={m.id} market={m} onSelect={handleSelect} />)}
Sorting on every keystroke
Type in both boxes. Raise the slider to feel it.
Sort in renderBad
- Last render
- –
- Sorts run
- 0
- Top market
- –
const sorted = [...markets].sort(byVolume)useMemo on [markets]Good
- Last render
- –
- Sorts run
- 0
- Top market
- –
useMemo(() => [...markets].sort(byVolume), [markets])const [searchQuery, setSearchQuery] = useState('')
const sortedMarkets = useMemo(
() => [...markets].sort((a, b) => b.volume - a.volume),
[markets]
)
const handleSearch = useCallback((query: string) => setSearchQuery(query), [])
The in-place sort
Click Render child. Watch the parent list.
markets.sort(…)Bad
Parent, A to Z
Child, by volume
[...markets].sort(…)Good
Parent, A to Z
Child, by volume
// sort() mutates and returns the same array
const sortedMarkets = useMemo(
() => [...markets].sort((a, b) => b.volume - a.volume),
[markets]
)
Why it matters, pros and cons
Why it matters
A parent render reruns every child. Memo reuses results while inputs keep identity.
Pros
- Unrelated state stops paying for big lists.
- One changed item re-renders one card.
- Stable references keep dependency arrays quiet.
Cons
- Cheap work costs more memoized; measure first.
- One inline arrow defeats
React.memo. - React Compiler already does most of this.