useMemo & useCallback
Keeping results instead of recomputing them
Both keep something between passes: useMemo a result, useCallback a function. Both are optimisations – not standard equipment.
On every draw your component runs top to bottom again. Usually that is cheap. Sometimes it is not – and then you can tell React: “as long as these values do not change, please reuse the earlier result.” That is called MemoisationKeeping a result around instead of computing it again.Along the lines of: “as long as the ingredients don't change, the result stays the same”.→.
const result = useMemo(() => expensive(a, b), [a, b]);
// ▲ function that PRODUCES the value ▲ what to watch
const fn = useCallback((x) => doSomething(x, a), [a]);
// ▲ the function ITSELF is kept A note on the fridge: “as long as a and b stay the same, the answer is this.” Change one of them and it recomputes – otherwise you get the old answer.
useCallback(fn, deps) is exactly the same as useMemo(() => fn, deps). It exists because keeping functions is such a common need.
There are exactly three good reasons
1. The computation really is expensive
(sorting thousands of entries, heavy formatting)
2. The value goes as a prop into a component wrapped in React.memo
-> without a stable reference memo does nothing
3. The value sits in a dependency list (useEffect and friends)
-> otherwise the effect runs on every draw Everything else only makes your code longer and harder to read. Wrapping a + b in useMemo costs more than it saves.
function List({ items, filter, onPick }) {
// Reason 1: expensive, and the result is displayed
const visible = useMemo(
() => items.filter((i) => i.name.includes(filter)).sort(compare),
[items, filter]
);
// Reason 2: goes into a memo component
const handleClick = useCallback((id) => onPick(id), [onPick]);
return visible.map((i) => <Row key={i.id} item={i} onClick={handleClick} />);
}
const Row = memo(function Row({ item, onClick }) {
return <li onClick={() => onClick(item.id)}>{item.name}</li>;
}); A stable function only helps if somebody compares it. Hand it to a plain <button onClick={…}> and you have only added work. The button compares nothing.
What React.memo does
React.memo is a wrapper around a component. It says: “if all the props are the same as last time, skip the drawing.” The comparison is Shallow comparisonReact only checks whether the values are the same on the surface – not whether two objects hold the same contents.→ – it does not look inside objects.
const Heavy = memo(function Heavy({ data }) { … });
<Heavy data={items} /> // ✅ same, as long as items is
<Heavy data={items.filter(Boolean)} /> // ❌ a new array every time
<Heavy style={{ color: "red" }} /> // ❌ a new object every time Passing a child in as children instead of drawing it inside prevents its redraw without any memo at all. And putting state as close as possible to where it is used works just as well. Structure first, memo second.
- +
Computations that actually show up in the ProfilerA measuring tool in the React DevTools that shows which component took how long.→
- +
Props for components wrapped in
React.memo - +
Values that end up in a dependency list
- −
useMemoarounda + b,items.lengthor a shortmap - −
Memoising “just in case, for later”
- −
useCallbackfor handlers that go straight to a<button>
▸ In more depth: costs, misunderstandings and the React Compiler optional
Each of these hooks takes memory and compares its list on every pass. For cheap computations that is more expensive than the computation itself.
The most common misunderstanding: “useMemo stops the component from redrawing.” It does not. The component still runs top to bottom – only that one computation inside is skipped. Redraws are prevented by React.memo, and only for child components.
The function inside useMemo runs in the middle of drawing. No fetch and no setState belong there – React may throw it away or repeat it.
The React Compiler (version 1.0 since 2025) inserts this optimisation automatically. Where it runs you hardly ever write useMemo or useCallback by hand. You still need to understand them – existing code is full of them.
Does it stick?
4 questions on this lesson. Wrong answers show up in your stats.
Now write it yourself
The list is refiltered on every draw – even when only the counter changed. Make it recompute only when the search box changes. The “Computations” line shows you how often it ran.
- □Filtering by
anshows only the matching entries - □Clicking
Counter +1causes no new computation - □A change in the search box causes one
import { useRef, useState } from "react"; const DATA = ["Banana", "Ananas", "Mandarin", "Cherry"]; export default function App() { const [filter, setFilter] = useState(""); const [count, setCount] = useState(0); const computations = useRef(0); // TODO: only recompute when filter changes computations.current += 1; const visible = DATA.filter((d) => d.toLowerCase().includes(filter.toLowerCase()) ); return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <input aria-label="filter" value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="filter …" />{" "} <button onClick={() => setCount((c) => c + 1)}>Counter +1 ({count})</button> <p data-testid="computations">Computations: {computations.current}</p> <ul> {visible.map((d) => ( <li key={d}>{d}</li> ))} </ul> </div> ); }