State Management in React, Revisited
In 2020 I argued Redux beat Context for frequently changing state. Six years and a real-time trading UI later: separate server state from client state, keep Context for things that rarely change, and use a store with selective subscriptions for anything hot.
On this page
In 2020 I wrote a short post comparing Redux and React Context. My conclusion: Context is easier, so use it for quick apps with infrequent state changes; Redux takes longer to learn but separates concerns well and performs better when state changes a lot.
That was roughly right, for slightly wrong reasons. Here’s how I’d answer the same question now.
First, split the problem
The biggest change since 2020 isn’t a library, it’s a distinction. Most of what we used to put in Redux was server state: data fetched from an API, cached, refetched and invalidated. Libraries like React Query handle that far better than a hand-rolled store of loading flags. What’s left is client state: UI state, selections, drafts, and in my world, data streaming in over a socket.
Once you separate the two, the “Redux or Context?” debate shrinks to a much smaller question.
Context is for things that rarely change
Context isn’t a state manager; it’s dependency injection. Every consumer re-renders when the value changes. That makes it perfect for theme, locale, the current user or feature flags, and a performance trap for anything that updates often.
I learned this the hard way on long-running screens. A context value that changed on every message re-rendered whole subtrees, and a re-render high in the tree could even remount children and duplicate their socket subscriptions. The investigation is written up in Finding React Re-render and Memory Leaks.
Hot state needs selective subscriptions
For state that changes many times a second, the rule is: only the component that displays a value should re-render when it changes. Two things make that possible:
- Selectors, so a component subscribes to one slice, not the whole store. Redux Toolkit and Zustand both do this well.
- Transient updates, where a component reads the latest value without going through React’s render cycle at all, then paints on its own schedule.
In a real-time trading UI at a fintech, moving a socket to a single owner and letting leaf components subscribe transiently was the difference between a janky screen and a smooth one (Zustand Transient Updates).
What I’d recommend today
| State | Where it lives |
|---|---|
| Server data (fetched, cached) | React Query or similar |
| Rarely changing app context (theme, user, flags) | Context |
| Shared client state, moderate frequency | A small store with selectors (Zustand, Redux Toolkit) |
| High-frequency streams (prices, positions) | A store outside React, with transient subscriptions and batching |
| Local UI state | useState in the component |
What my 2020 self got right
He was right that separating concerns matters, and right that Redux’s discipline (actions, a single store, logic away from JSX) makes large apps easier to reason about. Redux Toolkit has since removed most of the boilerplate he was willing to tolerate. I still use Redux on some codebases, and it’s fine. The mistake is putting everything in one global store, or anything hot in Context.
Originally written in 2020 → Redux vs React Context
- #react
- #redux
- #react-context
- #zustand
- #state-management
- #performance
↩ Linked from (0)
Nothing links here yet — it’s a fresh shoot.
↪ Links to (2)
Related by topic
- Growing
AG Grid Performance Patterns
Real-time UIs - Growingcase study
Building a Graph Component Library
Frontend craft - Growing
Graph Visualisation Libraries
Real-time UIs - Growing
Rust and WebAssembly: Benchmark Before You Bet
Frontend craft