React Performance: What Actually Matters

  • Performance
  • Published
  • Updated
  • 4 min read
React Performance: What Actually Matters

Almost every React performance article starts with memo and useMemo. In real applications those are rarely the problem, and applying them everywhere makes code harder to read while measurably changing nothing.

Here is the order I actually work in.

Step 1: Find out what is slow

Install React DevTools and open the Profiler tab. Record an interaction that feels slow, then look at the flamegraph. It tells you which components rendered and how long each took.

Also enable "Highlight updates when components render" in the DevTools settings. If a keystroke in one input flashes the entire page, you have found your problem in ten seconds.

Step 2: Bundle size, which is usually the real issue

On most sites, "React is slow" actually means "we ship 900 KB of JavaScript before anything renders."

  • Check what is in your bundle. A bundle analyser will usually surface one dependency you forgot about.
  • Import individual modules rather than whole libraries where the library supports it.
  • Lazy-load routes and heavy components with dynamic imports.
  • Remove dependencies you no longer use — they linger for years.
  • Ask whether a large UI library is earning its weight, or whether you are using four components from it.

Cutting 200 KB of JavaScript beats every render optimisation on this list combined.

Step 3: Fix state that lives too high

The most common genuine React performance bug is state placed higher in the tree than it needs to be. A form input storing its value at the page level re-renders the whole page on every keystroke.

The fix is not memo. The fix is moving the state down into the component that owns it, or extracting the stateful part into its own child so the siblings stop re-rendering.

This one change fixes more real-world slowness than everything else here.

Step 4: Lists

  • Use a stable, unique key. Using the array index breaks React reconciliation whenever the list reorders or filters.
  • Do not filter or sort a large array inside render on every keystroke — debounce the input.
  • Above roughly a thousand visible rows, virtualise so only what is on screen is rendered.

Below a few hundred rows, virtualisation adds complexity without a perceptible benefit. Do not reach for it early.

Step 5: Now consider memo, useMemo and useCallback

These are surgical tools with a real cost — memoisation itself uses memory and comparison time, and they make code harder to follow.

When memo genuinely helps

A component that renders often, has expensive output, and receives the same props each time. If the props change every render, memo is pure overhead.

When useMemo genuinely helps

A computation that is actually expensive — sorting thousands of items, heavy transforms. Not for a string concatenation. Also to keep object or array identity stable when it feeds a memoised child or a dependency array.

When useCallback genuinely helps

Only when the function is passed to a memoised child, or used in a dependency array. Wrapping every handler in useCallback is a widespread habit that mostly adds noise.

// Pointless - the object identity changes anyway
const style = useMemo(() => ({ color: 'red' }), []);

// Worth it - sorts a large dataset only when it changes
const sorted = useMemo(
  () => [...records].sort((a, b) => b.score - a.score),
  [records]
);

Step 6: useEffect misuse

A lot of perceived slowness is effects doing work they should not:

  • Deriving state in an effect and setting it — causing a second render. Compute it during render instead.
  • Missing dependencies causing stale values and extra runs
  • Fetching in an effect without cancellation, so a fast-typing user fires ten overlapping requests
  • Effects that could simply be an event handler

The rule of thumb: if the effect is not synchronising with something outside React, it probably should not be an effect.

Step 7: Images and fonts

Worth repeating because it dwarfs the rest. An unoptimised hero image costs more user-visible time than every re-render in your application. Fix assets before you fix renders.

Things not worth doing

  • Wrapping every component in memo "just in case"
  • useCallback on every function
  • Replacing React with a smaller library to save 40 KB while shipping 4 MB of images
  • Virtualising a fifty-item list
  • Micro-optimising render counts that no user can perceive

The summary

Measure first. Fix bundle size and assets. Move state down. Use stable keys. Only then reach for memoisation, and only where the profiler shows it matters. Most React apps that feel slow are not slow because of React.

Need help building this?

I take on web app, mobile and e-commerce projects. Tell me what you are building and I will reply within 24 hours with scope, timeline and a fixed quote.

Start a project