React state vs context vs state manager
By FrontendPro Editorial Team Updated 9/26/2026
Answer
Start with useState in the component that needs it. Move up only when you must, and reach for a library only when you have a real reason to. The decision has two axes: who needs the value and how often it changes.
The ladder
| Situation | Use |
|---|---|
| one component | useState / useReducer |
| a few nearby components | lift state to their closest common parent |
| deep, rarely-changing, app-wide | useContext |
| server data | a query library (React Query, SWR, or RSC + fetch) |
| large, frequently-changing, cross-cutting client state | Zustand, Jotai, Redux Toolkit |
| shareable, bookmarkable state | the URL |
Two rows get skipped in most discussions and matter most in practice.
Most "global state" is server data. A cache of what the server already knows needs fetching, caching, invalidation, retry, and staleness — none of which is a state-management problem. Putting it in Redux means reimplementing all of it by hand. React Query or SWR is the right tool, and it often removes the need for a global store entirely.
And a lot of the rest belongs in the URL. The current tab, filters, page number, an open modal — putting them in searchParams makes them shareable, survivable across refresh, and correct with the back button for free.
Why context is not a state manager
Context solves distribution, not storage. It has no selectors: every consumer re-renders when the provider's value changes, even consumers that only read one unchanged field.
// every consumer re-renders when either changes
<AppContext.Provider value={{ user, theme }}>
So context is a good fit for theme, locale, the current user, or a feature-flag object — values that change almost never. For something that updates on every keystroke it is the wrong shape, and the usual patches (splitting into several providers, memoising the value) are a sign you want a store with selectors.
When a library genuinely earns its place
- Many unrelated components subscribe to overlapping slices and you need selector-level subscriptions to avoid re-rendering the tree.
- The state has real transition logic worth centralising and testing apart from the UI.
- You want middleware: persistence, undo, devtools with time-travel, cross-tab sync.
Zustand for a small store with selectors and no boilerplate; Jotai when the state is naturally atomic; Redux Toolkit when you want strict conventions and a large team.
The failure mode in both directions
Reaching for Redux on day one gives you three files per feature to change a boolean. But threading a prop through six components because "we do not need global state" is the same mistake with more typing. Let the number of consumers decide, and revisit when it changes.
