Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    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.

    Growingtended planted 2 min read
    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

    StateWhere it lives
    Server data (fetched, cached)React Query or similar
    Rarely changing app context (theme, user, flags)Context
    Shared client state, moderate frequencyA small store with selectors (Zustand, Redux Toolkit)
    High-frequency streams (prices, positions)A store outside React, with transient subscriptions and batching
    Local UI stateuseState 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

    Nothing links here yet — it’s a fresh shoot.

    ↪ Links to (2)