Shikhil Saxena

May 20, 2026 • 4 min read

React Performance Isn’t About useMemo — It’s About Render Boundaries

Why slow React applications usually come from state placement, update scope, and architectural coupling - not missing memoization hooks.

When a React application starts feeling slow, the reflex is almost universal.

Add useMemo.

Thanks for reading! Subscribe for free to receive new posts and support my work.

Subscribe

Wrap callbacks with useCallback.

Throw memo() around suspicious child components.

A filtered list recalculates?

useMemo.

A function gets recreated?

useCallback.

A component rerenders unexpectedly?

memo.

The logic feels reasonable. The tools are built for optimization, the problem appears local, and the fix seems quick.

The issue is that this often mistakes the symptom for the disease.

Most React performance problems do not begin because someone forgot a hook.

They begin because the application has weak render boundaries.


Rerenders Are Not the Enemy

A surprising amount of React optimization advice starts from a flawed assumption:

rerenders are bad.

They are not.

React was built around rerendering.

State changes.

React recalculates UI.

That is normal operation.

The real question is not:

“Why did this component rerender?”

The better question is:

“Why was this component involved in this update at all?”

Performance problems usually appear when updates become:

  • too wide

  • too frequent

  • too expensive

Artwork: Relativity

Author: M.C. Escher

Too wide

One input field changes.

Half the page updates.

Too frequent

Every keystroke propagates through a heavy component tree.

Too expensive

Rendering triggers:

  • sorting

  • filtering

  • formatting

  • rebuilding large objects

  • recomputing derived state

The rerender itself is rarely the core problem.

The update scope is.


Example: Healthy rerender

Perfectly fine:

function Counter() {
 const [count, setCount] = useState(0)

 return (
 <button
 onClick={() => setCount(c => c + 1)}
 >
 {count}
 </button>
 )
}

Rerenders happen.

Nothing is wrong.

Optimization would solve nothing here.


The useMemo Misconception

useMemo is useful.

But it is not architectural medicine.

A common mistake looks like this:

const filteredUsers = useMemo(
 () =>
 users.filter(
 user =>
 user.name.includes(query)
 ),
 [users, query]
)

This may be perfectly appropriate.

Or completely irrelevant.

The missing question is:

why is this calculation running so often?

If every keystroke causes:

  • table rebuilds

  • sidebar rerenders

  • chart updates

  • modal recalculations

then memoizing one filter does not solve the architecture.

It simply places a performance-shaped bandage over a broader design problem.

Artwork: The Treachery of Images

Author: René Magritte


The Real Problem: State With No Boundaries

One of the most common React performance failures is state lifted too high.

A single parent component ends up owning everything.

function DashboardPage() {

 const [search, setSearch] =
 useState('')

 const [selectedRow, setSelectedRow] =
 useState(null)

 const [modalOpen, setModalOpen] =
 useState(false)

 const [loading, setLoading] =
 useState(false)

 const [tableData, setTableData] =
 useState([])

 const [errors, setErrors] =
 useState({})
}

Individually, nothing seems unusual.

Collectively, it becomes dangerous.

Now every small state change flows through the same parent.

Search input changes?

Large subtree rerenders.

Modal opens?

Table participates.

Loading flag updates?

Entire layout recalculates.

Developers often respond by layering optimization:

  • memo

  • useCallback

  • useMemo

The app becomes slightly quieter.

The architecture stays equally tangled.

The problem was never missing memoization.

The problem was missing boundaries.


State Should Live Close to Ownership

Good React optimization often begins with a surprisingly unglamorous move:

move state closer to where it actually belongs.

Bad:

Page
 ├── SearchBox
 ├── DataTable
 ├── Sidebar
 └── Modal

Everything depends on Page state.

Better:

Page
 ├── SearchSection
 │ └── search state
 │
 ├── DataTable
 │ └── row selection state
 │
 └── Modal
 └── visibility state

Now updates become localized.

Opening a modal does not rebuild unrelated UI.

Typing into search does not redraw the entire page.

Render boundaries become narrower.

React performs less work because less work exists.

Artwork: Broadway Boogie Woogie

Author: Piet Mondrian


Expensive Work Belongs Off the Hot Path

Not all rendering work carries equal cost.

Changing button text?

Cheap.

Sorting 20,000 rows after every keystroke?

Not cheap.

This is where optimization becomes practical.

Before adding hooks, map the hot path.

Ask:

What happens after the user types?

What changes after clicking a row?

Which components receive new props?

Which calculations rerun?

Once the update flow becomes visible, solutions become clearer.

Sometimes:

separate input from results

Sometimes:

virtualize large lists

Sometimes:

move formatting server-side

Sometimes:

the problem is not React at all.

The browser simply received far too much data.


Example: Expensive hot path

Bad:

function UsersTable({
 users,
 query
}) {

 const rows =
 users
 .filter(user =>
 user.name.includes(query)
 )
 .sort(sortUsers)
 .map(renderRow)

 return <>{rows}</>
}

Every keystroke:

  • filters

  • sorts

  • maps

Potentially across thousands of records.

Now useMemo becomes reasonable.

But only after understanding the workload.


React Compiler Will Not Fix Architecture

React Compiler changes the conversation.

Automatic memoization becomes increasingly possible.

Less repetitive optimization.

Less manual wrapping.

That is useful.

But it does not eliminate architectural thinking.

The compiler cannot decide:

  • where state belongs

  • which component owns data

  • why an entire screen depends on one object

Automatic optimization cannot untangle poor separation.

If one component mixes:

  • forms

  • analytics

  • tables

  • business rules

  • server state

  • UI state

no compiler magically transforms that into maintainable architecture.

Tools are getting smarter.

Boundary design still belongs to developers.

Artwork: Composition VIII

Author: Wassily Kandinsky


A Practical Checklist Before Reaching for useMemo

Before typing another optimization hook, ask:

What actually changed?

Why did that update reach this component?

Can state move lower?

Can static and interactive parts be separated?

Are oversized objects flowing through props?

Is heavy work happening on the user’s hot path?

Only after these questions does optimization become honest.

Then:

useMemo makes sense.

useCallback makes sense.

memo makes sense.

Without architectural understanding, they become decorative complexity.


Conclusion

React applications rarely become slow because someone forgot useMemo.

Much more often, they become slow because:

too much UI depends on too much frequently changing state.

Memoization can help.

It cannot replace understanding:

  • render flow

  • ownership

  • state placement

  • update boundaries

Strong React developers do not merely know where to place useMemo.

They understand:

what changed, why it changed, and what actually deserves to update.

React optimization does not begin with a hook.

It begins with a question:

What should actually change after this interaction?

Once that answer becomes clear, memo becomes a tool.

Until then, it is just another layer wrapped around architectural debt.

Join Shikhil on Peerlist!

Join amazing folks like Shikhil and thousands of other builders on Peerlist.

peerlist.io/

It’s available... this username is available! 😃

Claim your username before it's too late!

This username is already taken, you’re a little late.😐

0

1

1