Skip to main content
CodeOath
← All posts

React120 min total · 17 parts

React Fundamentals: Components, Hooks, and the Virtual DOM

Part 16 of 17 · ~3 min

Performance Optimization in Practice

Everything in chapter 9 was a tool. This is the order to use them in, and the order is what actually decides whether the work pays off — because every one of these has a readability cost, and applying them speculatively means paying that cost for nothing.

1. Measure first, with the Profiler. React DevTools' Profiler records a session and shows which components rendered, how many times, how long each took, and — with "why did this render" enabled — what triggered it. Our dashboard's real answer was the unmemoized sort, nothing like the eight hundred rows everyone had already pointed a finger at. Guesswork about where the slowness actually lives gets it wrong far more often than people expect, and guessing wrong buys you permanent complexity dropped into the wrong file.

2. Fix the cause before reaching for memoization. A surprising share of "React is slow" turns out to be a bug in the shapes from earlier chapters, and fixing those is strictly better than memoizing around them:

  • One large context re-rendering every consumer on every keystroke — split it (chapter 11).
  • An object or arrow function recreated inline and passed to a memoized child — stabilise it (chapter 9).
  • A component defined inside another component, remounting its whole subtree every render (chapter 4).
  • State living higher in the tree than it needs to. If the filter input's in-progress text lives on Dashboard, every keystroke re-renders the whole screen. Move that state into FilterBar and lift only the committed value, and most of the problem disappears without a single useMemo.

3. Then memoize the specific expensive thing. useMemo for the computation the Profiler flagged, React.memo plus stable props for the subtree that re-renders needlessly. Applied at the two places the measurement pointed at, not blanket across the app.

4. Keep the interaction responsive even when the work is real. Sometimes the expensive render is genuinely necessary and simply cannot be made fast. React 18's concurrent features let you keep the input responsive while it happens:

const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);      // lags behind during heavy work

const visible = useMemo(() => filterAndSort(candidates, deferredQuery), [candidates, deferredQuery]);

The input renders immediately with the new query, so typing stays instant, while the expensive list renders against a slightly older deferredQuery and catches up when it can. useTransition is the same idea with an explicit marker — startTransition(() => setQuery(next)) tells React that this update is interruptible and can be abandoned if a more urgent one arrives.

5. Virtualize long lists. For our eight hundred rows — and certainly for the eight thousand this table will hold next year — this beats every memoization strategy combined, because it changes the amount of work rather than avoiding repeats of it. A windowing library (react-window, @tanstack/react-virtual) renders only the rows inside the scroll viewport plus a small buffer, so the DOM holds thirty rows regardless of the data size. The cost is real: sticky headers, variable row heights, find-in-page and keyboard navigation all need deliberate handling. Worth it past a few hundred rows; overkill below that.

6. Split code by route and by feature. React.lazy for anything not needed to paint the first screen — our submission viewer, the analytics tab, the bulk-import wizard. This reduces JavaScript the browser must download, parse and execute before anything is interactive, which is usually a bigger win on a slow device than any render optimisation.

The through-line: the fastest render is the one that does not happen, and the second fastest is the one that renders thirty rows instead of eight hundred. Memoization only makes a render that is happening anyway slightly cheaper, which is the smallest of the three levers and, predictably, the one people reach for first.