Optimizing React Performance: Tips and Tricks
Profile first. Then fix the three things that are almost always the cause: unstable props, unnecessary context reads, and rendering lists you should be virtualising.
Most React performance advice starts with useMemo. That is the wrong place to start, and premature memoisation makes codebases worse while rarely making them faster.
Profile before you touch anything
Open React DevTools, enable Highlight updates when components render, and use the app. You are looking for components that flash on interactions that should not affect them. That flash is your problem. Everything else is speculation.
Then use the Profiler tab to record an interaction and read the flame graph. Sort by render duration. Fix the top item. Re-measure. Stop when it feels fast.
Cause 1: unstable props
This is the big one:
// New object and new function on every parent render.
<Chart config={{ height: 300 }} onSelect={(id) => setActive(id)} />
Chart re-renders every time regardless of whether it is wrapped in React.memo, because both props are new references each pass. Hoist the object out of the component, and wrap the callback in useCallback with correct dependencies.
The general rule: React.memo does nothing useful unless the props are referentially stable. Memoising a component while passing it fresh object literals is pure overhead.
Cause 2: context over-subscription
Every consumer of a context re-renders when any part of its value changes. A single context holding user, theme and cart means changing the cart re-renders everything that reads the theme.
Split contexts by update frequency. Things that change constantly should not share a provider with things that change once per session.
Cause 3: rendering lists you should virtualise
Rendering 5,000 rows creates 5,000 DOM subtrees, and the browser pays for all of them on every layout. Virtualisation renders only what is visible. Past roughly a few hundred rows, it is not an optimisation, it is a requirement.
Then, the free wins
- Code splitting. Route-level
lazy()plusSuspensecuts initial bundle size with almost no effort. - Defer offscreen work. Modals, tab panels and below-the-fold widgets do not need to be in the first bundle.
- Ship less JavaScript. Check your bundle analyser. A date library or icon set imported wholesale is often larger than your entire application code.
Know when to stop
Perceived performance is what users judge. An app that renders in 200ms with an instant loading skeleton feels faster than one that renders in 150ms with a blank white screen. Optimise the experience, not the number.
- React
- Performance
- Optimization