Skip to content
ReactField

TopicsIntermediate

Stop Unnecessary Re-renders in React

Stop costly React re-renders: paths for context blast and unstable props, symptom matching, memo rules and when not to optimize.

Mohamed ElshahawyPublished Apr 2026Updated Oct 2026
3 min read

What you'll learn

  • Paths for context blast, unstable callbacks, expensive children and Compiler-first apps
  • Match Profiler symptoms to the right fix
  • Five practical rules with before and after code
  • When memoization helps and when it is noise

React re-renders a component when state, props, or context changes. That behavior is how React keeps UI consistent with data.

Most re-renders are fine. The ones that hurt repeatedly run expensive work, or force large subtrees to repaint when nothing meaningful changed for that part of the UI.

Use the path picker when you know your situation. Use the symptom matcher when the Profiler shows a confusing commit. The rules below keep the code patterns.

Impact: smoother interactions and lower CPU usage in complex UIs

Four short paths from a Profiler flamegraph toward context split, stable callbacks, memoized leaves and the React Compiler
Start from the situation, then apply one rule. Do not wrap the tree in memo first.

Pick a re-render fix path

Try it: re-render fix path

Pick the situation you are in. You get an ordered approach, why it fits, and a first-week plan.

Your path

Split context before wrapping every child in memo

One mega context re-renders every consumer. Splitting values by update rate usually beats memoizing the whole tree.

  1. useContext

    Subscribe only to the slice you need.

  2. React DevTools Profiler

    Confirm which consumers commit on each update.

  3. State Management

    Move hot UI state out of shared context when it belongs local.

  4. React.memo

    Memo leaves only after the blast radius is small.

First week

Find one context that mixes auth, theme and prefs. Split theme out. Re-profile one screen that used to flash on login.

The rules below keep the code patterns. The path above is how you choose which rule to apply first.

Checkpoint

A mega context mixes auth, theme and prefs. Theme-only consumers re-render on login. What first?

Match a re-render symptom

Symptom chips connected to fix cards for memo, useCallback, split context and lazy useState
Name the symptom, then pick one primary fix.

Try it: match a re-render symptom

Choose what you see in the Profiler. The matcher points to a primary fix and an optional alternate.

The symptom

A child commits whenever the parent renders, even when its visible props look unchanged.

Start here

Why parent state causes child re-render

Parent state schedules a render pass. Children render unless memo and stable props skip them.

Also consider

React.memo

Memo only helps after you understand why the parent re-rendered.

Related: Why parent state causes child re-render

Most re-renders are fine. Fix the ones that burn CPU or jank interactions.

Key takeaway

Measure first. Stabilize props before trusting memo. Split hot context before wrapping the tree.

Rule 1: Lazy state initialization

If initial state setup is expensive, initialize lazily. For more loop and object patterns around this, see JavaScript Performance Patterns.

IncorrectTSX
function SettingsPanel() {
const [config, setConfig] = useState(
JSON.parse(localStorage.getItem('config') || '{}')
)
return <pre>{JSON.stringify(config, null, 2)}</pre>
}
CorrectTSX
function SettingsPanel() {
const [config, setConfig] = useState(() =>
JSON.parse(localStorage.getItem('config') || '{}')
)
return <pre>{JSON.stringify(config, null, 2)}</pre>
}

With lazy initialization, React calls the initializer only for the initial state setup, not during subsequent renders.

Rule 2: Stabilize callback references

Left: a parent passes a different key to its child every time and the child re-renders. Right: the parent passes the same orange key and the child stays calm
A new function on every render looks like a new prop. A stable reference lets memoized children skip work.

A memoized child still re-renders if it receives a new function reference every render.

Incorrect (inline callback prop)TSX
const Child = React.memo(function Child({ onSave }: { onSave: () => void }) {
return <button onClick={onSave}>Save</button>
})
function Parent({ id }: { id: string }) {
return <Child onSave={() => saveDocument(id)} />
}
Correct (useCallback)TSX
const Child = React.memo(function Child({ onSave }: { onSave: () => void }) {
return <button onClick={onSave}>Save</button>
})
function Parent({ id }: { id: string }) {
const onSave = useCallback(() => {
saveDocument(id)
}, [id])
return <Child onSave={onSave} />
}

React.memo only helps when props are stable references.

Rule 3: Memoize expensive derivations

If you repeatedly sort or filter big collections, compute once per dependency change.

Incorrect (recomputes every render)TSX
function ProductList({
products,
query,
}: {
products: Product[]
query: string
}) {
const visible = products
.filter((p) => p.name.toLowerCase().includes(query.toLowerCase()))
.sort((a, b) => a.price - b.price)
return <List items={visible} />
}
Correct (useMemo)TSX
function ProductList({
products,
query,
}: {
products: Product[]
query: string
}) {
const visible = useMemo(() => {
return products
.filter((p) => p.name.toLowerCase().includes(query.toLowerCase()))
.sort((a, b) => a.price - b.price)
}, [products, query])
return <List items={visible} />
}

Rule of thumb: memoize only if a computation costs more than about 1ms and profiling confirms it.

Rule 4: Stable object and array literals in props

Object and array literals create new references on every render.

Incorrect (new object each render)TSX
function Parent() {
return <Widget style={{ color: 'red' }} />
}
Correct (stable reference)TSX
const widgetStyle = { color: 'red' }
function Parent() {
return <Widget style={widgetStyle} />
}
// or inside component:
// const style = useMemo(() => ({ color: 'red' }), [])

Stable references reduce avoidable memoized-child updates. Deeper object-creation patterns live in JavaScript Performance Patterns.

Rule 5: Split context to avoid blast radius

Left: one large context sends signals to six components and all of them re-render. Right: two smaller contexts, and only the two components that read the changed one re-render
One big context re-renders every consumer. Split contexts limit the blast radius to the components that care.

A giant context causes broad re-renders because any value change updates all consumers.

Incorrect (single mega context)TSX
type AppContextValue = {
auth: AuthState
theme: Theme
userPrefs: UserPrefs
}
const AppContext = createContext<AppContextValue | null>(null)
Correct (split contexts)TSX
const AuthContext = createContext<AuthState | null>(null)
const ThemeContext = createContext<Theme>('light')
const UserPrefsContext = createContext<UserPrefs | null>(null)
// Components subscribe only to the context they need.

This limits re-render blast radius: auth changes should not repaint theme-only consumers.

Checkpoint

You wrapped a child in React.memo and it still commits every parent render. What is the usual cause?

React Compiler note (React 19)

If you are on React 19 with the React Compiler enabled, rules 3 and 4 are often automated by compile-time memoization. Rules 1, 2, and 5 still apply and remain important architectural practices.

When NOT to optimize

Do not optimize by default "just in case." Start with the React DevTools Profiler and production-like scenarios, then optimize the top offenders. Premature optimization adds complexity, increases bug risk, and can make code harder for teams to maintain.

Prefer this order:

  • Measure first
  • Optimize targeted bottlenecks
  • Re-measure to confirm impact

The key habit

Profile before memo. Split context before wrapping the tree. Stabilize props before blaming React.memo.

Re-render checklist

  • I profiled a production-like scenario before adding memo
  • Expensive initial state uses a lazy useState initializer
  • Memoized children get stable callbacks and object props
  • Expensive derivations use useMemo only when measured
  • Hot context values are split by update rate
  • I know whether the React Compiler covers my stack

Further reading

Conclusion

Re-renders are not the enemy. Unmeasured memo is. Fix identity and blast radius first, then cache the work that still shows up in the Profiler.

The key takeaway

Most re-renders are fine. Fix the ones that burn CPU or jank interactions, after you measure them.

Check your understanding

Test yourself6 questionsQuestion 1 of 6

A mega context re-renders theme-only consumers on login. What should you do first?

Recap

What to remember from this article

Sections read

0

Questions

0

none answered yet

Quiz

0

not taken yet

Up next: Eliminating Async Waterfalls · 4 min

Continue