Skip to content

Step 1 of 5 · Reading · ~4 min

Read

Production

Performance Optimization

When state changes, a component re-renders and, by default, so does everything below it, including children whose output cannot have changed. Most of the time that is fast enough, and the first rule is to measure before changing anything. When it is not, almost every fix is the same move: skip work when the inputs are the same as last time. After this lesson your program makes the exact decisions behind React.memo, useMemo and useCallback.

What "the same" means

The framework cannot look inside your data, so it asks one cheap question about each value, Object.is:

FirstSecondSame?
11yes
"ada""ada"yes
NaNNaNyes
0-0no
{a: 1}another {a: 1} written elsewhereno
an objectthat very objectyes

Primitives are the same when their value is. Objects and functions are the same only when they are the same object. Two objects with equal contents are different things.

In this lesson's script a value is written as text, and two ordinary values are the same when their text is equal. A value starting with @ stands for an object: @rows always names the same object, while the special @new is a fresh object each time it is written, like {} or () => {} typed inline in a component.

React.memo: compare props, skip the render

When a parent re-renders, a plain child re-renders too. A child wrapped in memo first compares its new props with the props of its last render. They are equal when both have the same set of names and every pair of values is the same. Order of the names does not matter, and an added or removed name makes them different. This comparison is shallow: it never looks inside an object. If they are equal, the previous output is reused and the child is skipped; otherwise it renders and the new props become the ones to compare against next time.

Why memo so often does nothing

Writing config={{ foo: 1 }} or onSave={() => save(id)} in a parent creates a new object or function on every render. Each one is different from the last by identity, so the memo child sees changed props every time and never skips. The fix is to make the value stable: hoist a constant out of the component, or keep it with useMemo (for objects) and useCallback (for functions).

useMemo and useCallback

Both remember one result together with the dependency list it was computed from. On the next render the new list is compared with the stored one, item by item with Object.is, exactly as in lesson 10. Same length and every item the same: reuse the stored result (a hit). Otherwise run the function, store the result and the new list (a miss). useCallback(fn, deps) is useMemo(() => fn, deps). A dependency that is itself a fresh object, like @new, can never hit.

These are performance hints, not guarantees: React may drop a cached value, so correctness must never rely on a hit. Memoising everything has a price too, since every memo costs a comparison and holds memory. Reach for it where a profiler shows a slow subtree, or where an expensive child receives props that really are stable.

The other levers

lazy plus Suspense splits code so a screen loads only what it shows. Virtualisation renders only the rows in the viewport of a long list, because rendering fewer nodes beats rendering them faster. useTransition and useDeferredValue mark updates as low priority so typing stays responsive. Server Components keep code off the client altogether. Solid and Svelte have no tree to re-render, and Qwik resumes instead of hydrating. Architecture, meaning where state lives and how data flows, usually wins over any micro-optimisation.

The script you will run

COMP Avatar memo         declare a component: memo or plain
RENDER Avatar user=ada   the parent renders it with these props
UNMOUNT Avatar           forget its last props
MEMO total 1 2           a useMemo call labelled total with deps 1 and 2

Common mistakes

  • Treating equal contents as equal objects. Only the same object is the same.
  • Comparing with the loose equality of your language so that 0 and -0, or 1 and 01, look equal.
  • Comparing against the first render instead of the last one that actually rendered.
  • Caring about the order of props, or ignoring an added or removed name.
  • Updating the stored props on a skip, or forgetting to update them on a render.

Your exercise

The starter runs the script and keeps the state. You write the four small decisions: same_value, props_equal, deps_equal and should_render.

Up nextProduction FrontendProduction

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…