React Performance Isn’t About useMemo — It’s About Render Boundaries
The React Systems Newsletter
SubscribeSign in
React Performance Isn’t About useMemo — It’s About Render Boundaries<br>Why slow React applications usually come from state placement, update scope, and architectural coupling - not missing memoization hooks.
The React Systems Newsletter<br>May 19, 2026
Share
When a React application starts feeling slow, the reflex is almost universal.<br>Add useMemo.<br>Thanks for reading! Subscribe for free to receive new posts and support my work.
Subscribe
Wrap callbacks with useCallback.<br>Throw memo() around suspicious child components.<br>A filtered list recalculates?<br>useMemo.<br>A function gets recreated?<br>useCallback.<br>A component rerenders unexpectedly?<br>memo.<br>The logic feels reasonable. The tools are built for optimization, the problem appears local, and the fix seems quick.<br>The issue is that this often mistakes the symptom for the disease.<br>Most React performance problems do not begin because someone forgot a hook.<br>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:<br>rerenders are bad.
They are not.<br>React was built around rerendering.<br>State changes.<br>React recalculates UI.<br>That is normal operation.<br>The real question is not:<br>“Why did this component rerender?”
The better question is:<br>“Why was this component involved in this update at all?”
Performance problems usually appear when updates become:<br>too wide
too frequent
too expensive
Artwork: Relativity<br>Author: M.C. Escher<br>Too wide
One input field changes.<br>Half the page updates.<br>Too frequent
Every keystroke propagates through a heavy component tree.<br>Too expensive
Rendering triggers:<br>sorting
filtering
formatting
rebuilding large objects
recomputing derived state
The rerender itself is rarely the core problem.<br>The update scope is.
Example: Healthy rerender
Perfectly fine:<br>function Counter() {<br>const [count, setCount] = useState(0)
return (<br>setCount(c => c + 1)}<br>{count}
}Rerenders happen.<br>Nothing is wrong.<br>Optimization would solve nothing here.
The useMemo Misconception
useMemo is useful.<br>But it is not architectural medicine.<br>A common mistake looks like this:<br>const filteredUsers = useMemo(<br>() =><br>users.filter(<br>user =><br>user.name.includes(query)<br>),<br>[users, query]<br>)This may be perfectly appropriate.<br>Or completely irrelevant.<br>The missing question is:<br>why is this calculation running so often?<br>If every keystroke causes:<br>table rebuilds
sidebar rerenders
chart updates
modal recalculations
then memoizing one filter does not solve the architecture.<br>It simply places a performance-shaped bandage over a broader design problem.
Artwork: The Treachery of Images<br>Author: René Magritte
The Real Problem: State With No Boundaries
One of the most common React performance failures is state lifted too high .<br>A single parent component ends up owning everything.<br>function DashboardPage() {
const [search, setSearch] =<br>useState('')
const [selectedRow, setSelectedRow] =<br>useState(null)
const [modalOpen, setModalOpen] =<br>useState(false)
const [loading, setLoading] =<br>useState(false)
const [tableData, setTableData] =<br>useState([])
const [errors, setErrors] =<br>useState({})<br>}Individually, nothing seems unusual.<br>Collectively, it becomes dangerous.<br>Now every small state change flows through the same parent.<br>Search input changes?<br>Large subtree rerenders.<br>Modal opens?<br>Table participates.<br>Loading flag updates?<br>Entire layout recalculates.<br>Developers often respond by layering optimization:<br>memo
useCallback
useMemo
The app becomes slightly quieter.<br>The architecture stays equally tangled.<br>The problem was never missing memoization.<br>The problem was missing boundaries.
State Should Live Close to Ownership
Good React optimization often begins with a surprisingly unglamorous move:<br>move state closer to where it actually belongs.<br>Bad:<br>Page<br>├── SearchBox<br>├── DataTable<br>├── Sidebar<br>└── Modal
Everything depends on Page state.Better:<br>Page<br>├── SearchSection<br>│ └── search state<br>├── DataTable<br>│ └── row selection state<br>└── Modal<br>└── visibility stateNow updates become localized.<br>Opening a modal does not rebuild unrelated UI.<br>Typing into search does not redraw the entire page.<br>Render boundaries become narrower.<br>React performs less work because less work exists.
Artwork: Broadway Boogie Woogie<br>Author: Piet Mondrian
Expensive Work Belongs Off the Hot Path
Not all rendering work carries equal cost.<br>Changing button text?<br>Cheap.<br>Sorting 20,000 rows after every keystroke?<br>Not cheap.<br>This is where optimization becomes practical.<br>Before adding hooks, map the hot path .<br>Ask:<br>What happens after the user types?<br>What changes after clicking a row?<br>Which components receive new props?<br>Which calculations rerun?<br>Once the update flow becomes visible, solutions become clearer.<br>Sometimes:<br>separate input from results<br>Sometimes:<br>virtualize large lists<br>Sometimes:<br>move formatting...