Abid Ali

Frontend Development

7 React Performance Mistakes and How to Fix Them

By Abid Ali · Published · Updated

Seven React performance mistakes that show up in real apps, and how to confirm each one in the profiler before adding memo or rewriting a component.

Cover illustration: 7 React Performance Mistakes and How to Fix Them
Cover illustration.

React performance mistakes are usually ordinary: a new object every render, a client bundle that did not need to be client code, an image that is larger than the slot it fills. Memo is the last tool, not the first. The react devtools profiler tells you whether a render is even on the critical path.

Record the interaction you care about (open a menu, type in a field, filter a list). Look at which components commit. Then fix the ones that show up, in the order below.

1. A client boundary that is too high

A use client layout or page pulls every import into the browser bundle. A header that tracks scroll does not need to client-render the article under it. Move the directive to the leaf. Unnecessary JavaScript delays input and startup more often than a wasted render does.

2. New objects and functions as props

This is the common cause of unnecessary rerenders once a child is memoized, and of wasted work even when it is not:

<SoundList options={{ sort: "new" }} onPlay={() => play(id)} />

options is a new object every time, and onPlay is a new function. If SoundList is wrapped in react memo, both props still change, so memo never bails out. Hoist the constant object. Wrap the handler in useCallback only if a memoized child depends on it. If nothing is memoized, the new function is cheap and the callback is noise.

3. Memo on everything

memo, useMemo, and useCallback are not free, and they make updates harder to read. Add react memo when the profiler shows a component rendering often with props that are often equal, such as a row in a long list. Do not wrap a button that renders once per page. Measure the commit. If the row is not hot, leave it alone.

4. Context that changes too often

A context value like { user, theme, setQuery } creates a new object every render and notifies every consumer. Split the fast-changing value (a search string) from the slow one (the current user). A soundboard search field should not rerender every sound button's favourite state if those are separate concerns.

5. Lists without stable keys, or with the index as the key

Index keys make React reuse the wrong row when the list is filtered or reordered. State inside the row (a playing flag, an open menu) sticks to the wrong item. Use a stable id from the data. Index keys are fine only for a static list that never reorders.

6. Images and client work blamed on React

A hero image that is a 4000px PNG, loaded on a phone, will dominate startup. Fix the file: a responsive source, a modern format, and fetchpriority="high" only on the image that is actually the largest paint. Lazy-load the ones below the fold. None of that is a React render problem. The profiler will not show it as a commit.

Meme Tap's grid of buttons has the same shape. The buttons can be simple. The audio files should not all download on first paint. Load a sound when it is played.

7. Derived state stored in state

Copying props into useState, or storing a filtered list that useMemo could compute, leads to extra updates and stale values. Compute the filtered list during render. Store the query string, not the query's result. An effect that sets state whenever props change is an extra render by design.

A loop that stays honest

  1. Record one slow interaction in the react devtools profiler.
  2. Name the components in that commit.
  3. Fix the highest one using the list above.
  4. Record again.

If the commit was already cheap, stop. React performance mistakes get worse when every file gains a memo "for safety" and the real cost was a 1 MB image. The profiler is the record. A guess about rerenders is not.

Questions

Should every component use React.memo?
No. Memo has a cost and it hides nothing if the parent passes new objects each render. Profile first. Memo a component when the profiler shows it rendering often with the same output.
Where do I look for wasted renders?
React DevTools Profiler. Record an interaction, then inspect the commits. A component that renders because a parent rendered, with no prop change that matters, is the usual waste.
Is a slow page always a React problem?
No. Large images, render-blocking fonts, and too much client JavaScript often dominate. Check the network and the bundle before tuning render cycles.

Next in the reading path: How to Find and Fix Broken Internal Links

Let's discuss your next project.

Tell me what you are building, who it is for and what a good result looks like. I'll reply with questions, a suggested approach and the next steps.