Lesson 7 of 9 · 22m
React Performance: Measure, Memoize, Defer
The real perf playbook — find what's actually slow before optimizing, and the three tools (memo, transitions, code splitting) that fix 95% of it.
React performance advice on the internet is mostly superstition. The truth: renders are cheap, DOM work is expensive, and the slow things in your app are almost always (1) doing too much in render, (2) re-rendering giant trees, or (3) shipping megabytes of JavaScript. All three are measurable — measure first.
The profiler is the entry point
React DevTools' Profiler records a render, shows you which components rendered, how long each took, and why. Optimize what it shows you, not what feels slow. Common real-world findings: a context provider re-rendering the whole tree, a list of 500 items that should be virtualized, an effect doing synchronous work that blocks paint.
Memoization is a scalpel, not a lifestyle
const MemoRow = memo(Row); // skip re-render when props are equal
const total = useMemo(() => expensive(items), [items]);
const onSelect = useCallback((id) => setSelected(id), []);memo only helps if props are referentially stable — which is why useMemo/useCallback exist (they hold references stable). The catch: a useCallback wrapping a function passed to a non-memoized child is overhead with no benefit. Apply memo at list-item boundaries and known-hot leaves; don't wrap everything.
Concurrent features for perceived speed
const [isPending, startTransition] = useTransition();
startTransition(() => setQuery(next)); // keep the old UI responsive while the heavy render happensTransitions mark updates as non-urgent — React can interrupt them to keep typing and clicks snappy. useDeferredValue does the same for a value: type into a search box and let the expensive results list lag one frame behind.
The biggest wins are structural
- Code splitting:
const Settings = lazy(() => import("./Settings"))inside<Suspense>— ship the admin panel only when it's opened. - List virtualization: react-window/TanStack Virtual — render the 40 visible rows of 10,000, not all of them.
- Avoid layout thrash: reading
offsetHeightin a loop forces synchronous layout; batch reads or useResizeObserver.
Field notes
- A 50ms render is "slow" only if it happens on every keystroke — measure per-interaction.
- React Compiler (auto-memoization) is real and shipping; it makes manual memo mostly unnecessary for new code — learn the model anyway so you can debug it.