Command Palette

Search for a command to run...

Hectal
PHASE 16Advanced React 16.8+ ~22 min· topic 4 of 10

Topic 16.4

useMemo and useCallback

In one line

useMemo(fn, deps) caches the result of a calculation and useCallback(fn, deps) caches a function, both until a dependency changes; they help when a calculation is slow or when a stable reference lets memo or an effect skip work, and they are noise everywhere else.

Think of it like this

A homework answer you already worked out. If tomorrow's question is exactly the same, you copy yesterday's answer instead of solving it again. If one number in the question changed, you solve it fresh. useMemo keeps yesterday's answer and checks whether the question (the dependencies) changed.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

useMemo
A hook that remembers the result of a calculation and recomputes it only when a dependency changes.
useCallback
A hook that gives back the same function object across renders until a dependency changes.
Dependencies
The list of values a cached result depends on. If one changes, the cache is refreshed.
Stable reference
A value that is the very same object from render to render, so Object.is says it didn't change.
Expensive calculation
Work that takes noticeable time, roughly 1 ms or more, like sorting thousands of items.
Cache
A stored result kept for reuse.

Step by step

01Spot a repeated calculation

A product page filters 2,000 products by price. A dark-mode toggle in the same component re-renders it, and the filter runs again with the same inputs.

02Wrap it in useMemo

List what the calculation reads: products and maxPrice. Those are the dependencies.

Products.jsxwhole filejsx
const visible = useMemo(
  () => products.filter((p) => p.price <= maxPrice),
  [products, maxPrice],
);

03How React decides

Each render React compares the dependencies with the last ones. Same: return the cached value. Different: call the function and cache the new value.

How React decidesdiagram
Rendering diagram…

04Stabilise a callback for a memo child

A memoised SaveButton receives onSave. Without useCallback, onSave is new each render and the memo never skips. With useCallback(fn, []), the reference stays the same. Use an updater function (setSaved((s) => s + 1)) so the callback needs no dependencies (Topic 4.5).

05Stabilise an effect dependency

An object built during render and used in an effect's dependency list re-runs the effect every render. Memoise the object, or better, build it inside the effect from primitive values so the dependencies are strings and numbers.

Chat.jsxwhole filejsx
// Better than useMemo: create the object inside the effect.
useEffect(() => {
  const options = { serverUrl, roomId };
  const conn = createConnection(options);
  conn.connect();
  return () => conn.disconnect();
}, [serverUrl, roomId]);

06Measure the calculation

Wrap it with console.time("filter") and console.timeEnd("filter") in a production build, ideally with CPU throttling on in the browser's DevTools. Under about 1 ms, skip useMemo.

Try it yourself

  1. 1

    Forget a dependency

    In the first example, change the dependency list to []. Predict the screen after clicking Max price. The cached count stays stuck at the old value: the cache was never refreshed. Put maxPrice back.

  2. 2

    A dependency that changes every time

    Add const options = { maxPrice }; above the useMemo and use [options] as the dependency (reading options.maxPrice inside). Predict memo = after three toggles. A new object each render makes the cache useless.

  3. 3

    Add a dependency to the callback

    Make saveStable log the current text: useCallback(() => { console.log(text); setSaved((s) => s + 1); }, [text]). Predict when the useCallback button renders now. It re-renders whenever text changes, because the callback must change to see the new text.

Code & diagrams

Counting calculations with and without useMemo New tab

Five renders in total. The plain filter ran five times, the memoised one twice: once on mount and once when maxPrice changed. (Writing to outside counters during render breaks purity; it's done here only to count. In StrictMode the numbers would double.)

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

After 3 dark-mode toggles: plain = 4 memo = 1
After 1 price change:      plain = 5 memo = 2
useCallback makes memo work New tab

The script types two letters. The inline-handler button renders on every keystroke; the useCallback one is skipped. Both buttons still work: try clicking them.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

App rendered, text = ""
  Inline handler rendered
  useCallback handler rendered
App rendered, text = "h"
  Inline handler rendered
App rendered, text = "hi"
  Inline handler rendered
useCallback is useMemo for functionsjsx
// These two lines give the same stable function:
const onSave = useCallback(() => save(id), [id]);
const onSave2 = useMemo(() => () => save(id), [id]);

// Noise: a cheap value, and a callback passed to a plain DOM element.
const total = useMemo(() => price * qty, [price, qty]);   // just write price * qty
<button onClick={useCallback(() => setOpen(true), [])}>Open</button>; // <button> isn't memoised

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Stale value from an empty dependency list

Write const saveStable = useCallback(() => setSaved(saved + 1), []); and click the useCallback button three times.

terminal
$ Preview
── what you'll see ──
Saved 1 times
(the count never goes above 1)

Myth vs fact

Myth

useCallback stops the function from being created on every render.

Fact

The arrow function is still created every render. useCallback only returns the cached one instead, so its reference stays stable.

Myth

useMemo everywhere makes an app faster.

Fact

For cheap values it's slower and noisier: React must store the value and compare dependencies every render. Use it for slow calculations or when a stable reference is consumed.

Myth

useMemo guarantees the function runs only when dependencies change.

Fact

It's a performance hint. React may discard the cache, and in StrictMode development calls the function twice. Code must be correct either way.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Both hooks store [value, deps] on the hook object in the fiber's memoizedState list. The comparison is areHookInputsEqual: a loop of Object.is over the arrays. Changing the dependency array's length between renders logs an error in development.

  • ▸

    In StrictMode development, React calls the useMemo function twice on a recompute and uses one result, to surface impure calculations. Don't put side effects there.

  • ▸

    A context provider value={{ user, logout }} is the most valuable place for useMemo in hand-written code: without it every reader re-renders on every provider render (Topic 9.3).

  • ▸

    With the React Compiler, manual useMemo/useCallback are mostly redundant. The React team advises keeping existing ones (removing them can change what the compiler outputs) and not adding new ones unless you need precise control, such as a value used as an effect dependency.

Remember this

  1. 1

    **useMemo** is a hook (Phase 4.3 rules apply): const result = useMemo(() => compute(a, b), [a, b]). On the first render React calls your function and stores the result. On later renders it compares each dependency with its previous value using Object.is. If all are equal, it returns the stored result without calling your function. If any changed, it calls the function again and stores the new result. The dependency list must include every value from the component that the function uses (the react-hooks/exhaustive-deps lint rule checks this, Topic 7.2).

  2. 2

    **useCallback caches a function** instead of a result: const onSave = useCallback(() => save(id), [id]). It returns the same function object across renders until id changes. It is exactly useMemo(() => fn, deps). The function itself is still created on every render (that's how JavaScript works); useCallback just hands you the old one when nothing changed. So it doesn't make anything faster by itself: it gives a stable reference.

  3. 3

    Use 1: skip an expensive calculation. Filtering or sorting thousands of items, building chart data, parsing a big text. If the component re-renders for unrelated reasons (a theme toggle, typing in another field), useMemo avoids repeating the work. A rule of thumb from the React docs: if the calculation takes about 1 ms or more (time it with console.time in a production build), memoising may be worth it.

  4. 4

    Use 2: keep a reference stable for something that compares references: a prop of a memo component (Topic 16.3), a value in an effect's dependency list (so the effect doesn't re-run every render), a context provider's value, or a dependency of another useMemo. Without that consumer, a stable reference buys nothing.

  5. 5

    When they're noise. Wrapping cheap expressions (useMemo(() => a + b, [a, b])) or functions passed to plain DOM elements (<button onClick={useCallback(...)}>) adds code, dependency bugs and a little memory, and saves nothing. Dependencies that change every render (an inline object) make the cache useless. And useMemo is a performance hint, not a guarantee: React may throw the cache away (for example for components that suspend during their first render), so your code must still be correct if the function runs again.

  6. 6

    Better than memoising. Often the calculation can be avoided: compute derived data during render without state (Topic 6.2), move the expensive part into a child that owns the changing state, or filter on the server. And with the React Compiler (Topic 16.5) the compiler inserts this caching automatically, so new code in compiled projects rarely needs either hook.

Explain it without notes

01

What is the difference between useMemo and useCallback?

02

Give two situations where useMemo or useCallback really help.

03

Why is useCallback useless when the function is passed to a plain <button>?

04

What problems can a wrong dependency list cause?

Practice

01

A SearchResults component sorts 5,000 products by sortKey and also has an isMenuOpen state. Cache the sort so opening the menu doesn't sort again.

02

A memoised <TodoItem onToggle={...} /> list re-renders every item when one is toggled. Make the callback stable.

Trade-offs

  • ↔

    Caching saves repeated work but costs memory, dependency bookkeeping and readability, and wrong dependencies cause stale-data bugs.

  • ↔

    Manual memoisation gives precise control; the React Compiler gives the same benefit everywhere with less code but needs your components to follow the Rules of React.

Done when you can

  • I can cache a slow calculation with useMemo and the right dependencies.

  • I can stabilise a callback with useCallback for a memo child.

  • I can tell when these hooks are noise.

  • I can explain stale closures caused by missing dependencies.

  • I know useMemo is a hint, not a guarantee.