Skip to content

React Performance

React is fast by default for most apps. Performance work starts with measuring — React DevTools Profiler identifies actual bottlenecks before you add memoization or splitting.

Measure Before Optimizing

Common causes of slow React apps: unnecessary re-renders, large lists without virtualization, unoptimized images, and heavy computation during render — not React itself being slow.

React 18 concurrent rendering, automatic batching, and transitions (useTransition) help keep UIs responsive during expensive updates.

import { Profiler } from 'react';

<Profiler id="Dashboard" onRender={(id, phase, actualDuration) => {
  console.log(`${id} ${phase}: ${actualDuration}ms`);
}}>
  <Dashboard />
</Profiler>

Profiler reports how long commits took — use it to find slow subtrees.

Performance Toolkit

React DevTools Profiler  → find slow components
useMemo / useCallback     → memoize values/functions
React.memo                → skip re-renders
lazy + Suspense           → code splitting
useTransition             → non-urgent updates
Virtualization            → large lists
  • Profile in production-like builds — dev mode is slower.
  • Fix architecture before sprinkling memo everywhere.
  • Split contexts to reduce subscriber re-renders.
  • Move heavy work off main thread or web workers if needed.

Performance Checklist

Steps before and during optimization.

Step Action
Profile React DevTools Profiler
Identify Unnecessary re-renders or slow render
Fix root cause State colocation, split context
Memoize React.memo, useMemo, useCallback
Split code React.lazy + Suspense
Virtualize Long lists with TanStack Virtual

Why Components Re-render

Parent re-render, context change, state change, or prop reference change all trigger child re-renders unless memoized. Not every re-render is expensive — profile to find costly ones.

useTransition for Responsive UI

useTransition marks state updates as non-urgent, letting React keep the UI responsive during heavy renders like filtering large lists.

const [isPending, startTransition] = useTransition();
startTransition(() => setFilter(value));

Common Mistakes

  • Optimizing without profiling first.
  • Memoizing everything by default.
  • Blaming React for unvirtualized 10k-row tables.
  • Ignoring bundle size and image optimization.

Key Takeaways

  • Measure with Profiler before optimizing.
  • React 18 batching and transitions improve perceived speed.
  • Fix state architecture before heavy memoization.
  • Combine memo, splitting, and virtualization as needed.

Pro Tip

Record a Profiler session while performing the slow user action — compare flamegraphs before and after changes.