All writing

Why I Stopped Reaching for State Management Libraries

I’ve had the same conversation enough times that I might as well write it down. A junior or mid-level engineer wants to add Redux to a project that has almost no shared state to speak of. We talk it through, and when we get to the bottom of it, the reason usually isn’t the problem in front of us. It’s that Redux is interesting and they want to use it.

I understand the pull completely, because I’ve felt it. But it’s a bad reason to add a dependency, and somewhere over the last few years I stopped reaching for state-management libraries by default and started getting a lot more out of what React already hands me.


What I Reach for Now

These days my default is React’s own state and Context, and I add Zustand only when I actually have to. In most of the apps I’ve worked on, Redux and Redux Toolkit were solving a problem more elaborately than it needed solving.

To be fair up front: this isn’t the tired “Redux has too much boilerplate” complaint. Redux Toolkit fixed most of that years ago. My issue isn’t the ceremony, it’s that the whole apparatus is an answer to a question a lot of apps simply aren’t asking.

When I feel the urge to reach for a store, it’s usually a signal that state is living in the wrong place, not that I need a new tool to manage it. Lift it to the right level, shape the component tree sensibly, and the need for a global anything tends to shrink on its own. It’s the same instinct I pushed back on in A Case Against Abstraction: adding structure to feel in control, when the structure is the thing making it complicated.


The Taxonomy That Dissolves the Question

Most arguments about state management get easier once you stop treating “state” as one thing. There are at least four kinds, and they want different tools.

Most state is local. It belongs to one component or a small subtree, it never needs to travel, and useState or useReducer is the whole answer. The mistake is globalizing state that was perfectly happy where it was.

Shared UI state is what Context is for, within limits. Context is great for values that many parts of the app read but that change rarely: theme, the current user, locale, feature flags. What it is not is a state manager, and treating it like one is exactly where people get burned. Every consumer re-renders when the value changes, so the moment you put fast-changing state behind a single broad Context, you’ve built yourself a re-render problem. Context distributes state. It doesn’t manage it.

Most of what you wanted Redux for was server state. This is the one that quietly removes most of the perceived need for a store. A huge share of what people historically dumped into Redux was cached server data: API responses, loading flags, refetch logic. That isn’t client state at all. A query library like TanStack Query or RTK Query handles caching, deduplication, and revalidation far better than a hand-rolled slice ever will. Once you pull server state out into something actually built for it, the “store” you were sure you needed is often close to empty.

The genuine ten percent. What’s left is the case that really does want a library: client state that’s genuinely global, changes often, and has many independent subscribers. That’s precisely where Context’s re-render model falls apart and a selector-based store earns its keep. This is when I reach for Zustand, which gives me the selector subscriptions I want without a provider tree or much ceremony. And occasionally Redux Toolkit, when the app is large enough that its conventions are worth more than its weight.


What’s Actually Good About Redux

I want to be clear that I’m not anti-Redux. The selector pattern is genuinely good. useSelector subscribing a component to exactly the slice it cares about, and re-rendering only when that slice changes, is a clean idea, and it’s no accident that Zustand and most of the newer libraries copied it.

For a large app with many engineers, the rigidity that feels like overhead on a small project becomes a feature. Conventions keep ten people from inventing ten different patterns, and the devtools and predictable update model are real. So the objection was never that Redux is a bad piece of technology. It’s that reaching for it first, on an app that doesn’t have the problems it solves, is choosing the tool before you’ve understood the job.


Where My Approach Breaks

My own default has failure modes, and I’ve walked into most of them.

The common one is fleeing Redux straight into a single giant Context and recreating the exact re-render storm you were trying to escape, except now without the devtools. “Just use Context” is advice that quietly assumes you’ll use it for the right things. Misused, it’s its own mess, and an uglier one.

On a big team, “with the right architecture” is carrying a lot of weight in my argument, and I know it. The right architecture assumes someone is maintaining the architecture. Redux’s structure buys a consistency that a sprawl of bespoke hooks and contexts will not, and past a certain size that consistency can be worth more than the satisfaction of doing it all by hand.

And minimalism curdles into its own ego if you let it. “I’m too senior to need Redux” is just résumé-driven development with the sign flipped. The goal was never to use the fewest libraries. It’s fit. Sometimes the thing that fits is Redux.


The Rule

My actual rule is boring, which is how I prefer my rules. Keep it stupid simple, and before adding a state library, make yourself answer one question: what does this solve that React and a query library don’t, and is that problem actually in this app?

If you can name it, add the dependency with a clear conscience. If you can’t, you’re adding it because it’s interesting, and interesting is a reason people genuinely choose technology and a poor one to choose it on. So the next time you reach for a store, here’s the thing to ask first: once you take the server data out, what’s actually left in it?

Email address copied [email protected]