React state management in 2026: choosing between five real options
Most 'state management' debates are category errors. Server data and UI state are different problems and need different tools.
React re-renders a component when its state changes, its parent re-renders, or a context it consumes updates. Most performance problems come from state living too high in the tree or from context carrying values that change frequently — not from missing memo calls. Profile first, then move state down, split contexts, stabilise props, or memoise the specific expensive subtree.
When its own state or a reducer updates, when its parent re-renders, or when a context it subscribes to changes value.
Note what is not on that list: prop values changing. A child re-renders because its parent re-rendered, whether or not the props it received are different. React re-runs the function and diffs the output; only actual DOM differences reach the browser.
This matters because it reframes the problem. The question is not 'how do I stop this component re-rendering' but 'why is a component this expensive running this often'.
The most common structural mistake is a piece of state used by one small component sitting in a page-level parent. Every keystroke in a search box then re-renders the entire page tree.
// Before: query lives at the top, whole page re-renders per keystroke
function Page() {
const [query, setQuery] = useState("");
return (
<>
<ExpensiveChart />
<SearchInput value={query} onChange={setQuery} />
<Results query={query} />
</>
);
}
// After: state lives with the components that use it
function Page() {
return (
<>
<ExpensiveChart />
<SearchPanel /> {/* owns query, re-renders alone */}
</>
);
}A context provider re-renders every consumer whenever its value changes identity. Bundling a user object, a theme, and a frequently-updating cart total into one context guarantees that a cart update re-renders every themed component in the app.
React.memo compares props shallowly. Passing an inline object or arrow function creates a new reference on every render, so the memoised child re-renders anyway and you have paid for the comparison. Stabilise first with useCallback and useMemo, then wrap the component.
| Tool | Use when | Do not use when |
|---|---|---|
| React.memo | Component is genuinely expensive and props are stable | Component renders in under a millisecond |
| useMemo | A computation is measurably expensive | Wrapping a string concat or small array map |
| useCallback | The function is a dependency or a memoised child's prop | The consumer is a plain DOM element |
| Virtualisation | Rendering hundreds of rows | Rendering a dozen |
With the React Compiler now available, much of this memoisation can be applied automatically at build time. That does not remove the need to place state well — the compiler optimises what you wrote, it does not restructure your component tree for you.
No. Each memo adds a props comparison on every render and prevents nothing when props are unstable. Apply it to specific components you have profiled and found expensive.
No. React re-runs the component and diffs the result; only differences are committed to the DOM. Re-renders cost JavaScript time, not necessarily paint time.
StrictMode intentionally double-invokes renders and some effects in development to surface impure logic. It does not happen in production builds.
For most memo, useMemo and useCallback usage, yes — it inserts equivalent optimisations automatically. Structural issues like badly placed state or oversized contexts still need a human.
Harshal Patel
Founder & Lead Engineer, ROVQIX
Harshal leads engineering at ROVQIX, where he has shipped production Next.js, Node.js and PostgreSQL systems for startups, SaaS teams and ecommerce brands. He writes about the trade-offs behind architecture decisions rather than the framework of the week.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Most 'state management' debates are category errors. Server data and UI state are different problems and need different tools.
Every codebase is well organised on day one. The question is what it looks like after forty feature requests have been bolted onto the same Button.
INP is the metric that exposes how much JavaScript you shipped. A page can load in a second and still feel broken when tapped.
No spam. Just the occasional case study and craft breakdown.