Cheat SheetsReactPerformance

Performance — Cheat Sheet

React · 4 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Performance
React4 topicsQuick revision reference
1

Diagnosing Unnecessary Re-renders

Find the cause before applying a fix. Nearly every "React is slow" report turns out to be one of four specific causes, and three of them are fixed by moving code rather than memoising it.

  • A re-render is only a problem when it is expensive or cascades widely — measure before optimising
  • The four causes are state too high, an unmemoised context value, an unstable prop defeating memo, and genuinely expensive work
  • Highlight-updates and the Profiler's "why did this render" identify the cause in seconds
  • Moving state down and passing children through fix most cases with no memoisation at all
  • StrictMode doubles development renders — verify counts in a production build, on a throttled mid-range device
Four causes, three structural fixes
// 1. State too high — a keystroke in a search box re-renders a
//    2,000-row table that does not read the query
function Page() {
  const [query, setQuery] = useState('')     // move this into SearchBox
  return <><SearchBox value={query} onChange={setQuery} /><BigTable /></>
}
// Fix: move the state down, or pass BigTable as children.

// 2. Context value recreated every render
<Ctx.Provider value={{ user, signOut }}>    // new object each time
// Fix: useMemo the value; split contexts.

// 3. Unstable prop defeating memo
<MemoRow onSelect={() => pick(id)} />        // new function each render
// Fix: useCallback, or pass the id and a stable handler.

// 4. Genuinely expensive render — a big list, a chart, a heavy
//    markdown or syntax-highlighting pass
// Fix: useMemo the computation, virtualise the list, or split the
//      work with useDeferredValue.

// Causes 1-3 are fixed by structure. Only 4 needs memoisation.
2

Profiling and Measuring

Two different questions need two different tools: "which component is slow" is the React Profiler, "is the page slow for users" is Lighthouse and field metrics.

  • The React Profiler answers "which component is slow"; Lighthouse and field metrics answer "is the page slow"
  • Read a profile as: how many commits, largest self duration, and why did it render
  • A frame is 16ms — renders longer than that drop frames and show up as INP
  • Verify in a production build, since development React is slower and StrictMode doubles renders
  • For a slow-loading app, analyse the bundle first — the cause is usually one oversized dependency on the critical path
Commits, self duration, and why
// React DevTools -> Profiler -> record -> interact -> stop

// The flame chart shows, per commit:
//   - which components rendered
//   - actual duration (this component and its children)
//   - self duration (this component alone)
//   - WHY it rendered, with "Record why each component rendered" on

// Read it in this order:
//   1. How many commits did one interaction cause? Several is a
//      cascade — usually chained effects or state set during render.
//   2. Which component has the largest self duration?
//   3. Why did it render — props, state, hooks, or parent?

// The Profiler API for production measurements
<Profiler id="ProblemList" onRender={(id, phase, actual, base) => {
  if (actual > 16) analytics.track('slow_render', { id, phase, actual })
}}>

// 16ms is one frame at 60fps. Anything longer drops a frame.

// Always confirm in a production build: development React is far
// slower, and StrictMode doubles the render count.
3

Shipping Less JavaScript

The fastest code is the code you never send. Splitting, deferring and choosing smaller dependencies beat almost any runtime optimisation.

  • Splitting by route is the highest-value change; then heavy libraries, below-the-fold content and conditional features
  • Stop splitting when the extra round trip costs more than the bytes saved
  • fetch, Intl and crypto.randomUUID have replaced several once-standard dependencies
  • Third-party scripts often outweigh the app bundle — load them after interactive
  • Virtualisation renders only visible rows and is worth it above roughly 200 items, at the cost of in-page find
Route, heavy library, below the fold, conditional
// 1. By route — the biggest single win
const Admin = lazy(() => import('./features/admin/AdminPage'))

// 2. Heavy libraries, loaded on demand
async function exportPdf(rows) {
  const { jsPDF } = await import('jspdf')     // 300KB, only on click
  ...
}

// 3. Below the fold — comments, recommendations, related items
const Comments = lazy(() => import('./Comments'))
<Suspense fallback={<CommentsSkeleton />}>
  {inView && <Comments />}                     // with an observer
</Suspense>

// 4. Conditional features — an editor only Pro users open,
//    an admin toolbar, a debug panel

// Where to stop: a small component on the critical path. An extra
// round trip costs more than the few kilobytes saved, and too many
// chunks is its own problem.
4

Perceived Performance

How fast an interface feels is a separate problem from how fast it is. Responding immediately, showing structure early and never moving content account for most of it.

  • Acknowledge every interaction within 100ms even when the work takes longer
  • Optimistic updates and prefetch-on-hover can make an action feel instantaneous
  • Stream content in parts with separate Suspense boundaries rather than waiting for everything
  • Show cached or partial data immediately and fill in the rest, instead of clearing to a skeleton
  • Reserve space for late-loading content and delay spinners, because layout shift and flashing feel worse than waiting
100ms, 1s, 10s
// < 100ms   feels instant — no feedback needed
// < 300ms   noticeable — a subtle state change suffices
// < 1s      the flow of thought holds — show a spinner or skeleton
// > 1s      attention drifts — show progress and what is happening
// > 10s     they leave — show progress, an estimate, and a way to cancel

// So: acknowledge EVERY interaction within 100ms, even if the work
// takes longer. The button changes state on click, not on response.

<button onClick={run} disabled={isRunning}>
  {isRunning ? 'Running…' : 'Run'}        // instant acknowledgement
</button>

// Optimistic updates put the result on screen immediately for
// actions that almost always succeed — a like, a toggle, a reorder.
// A perceived 0ms, with a rollback if the server disagrees.

// Prefetch on intent: hovering a link starts the request ~250ms
// before the click, which is often the entire request time.
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/react