There's a specific kind of shame that comes from opening a pull request review and seeing npm install @reduxjs/toolkit react-redux as the first line of the diff, for a feature that turned out to be a dropdown and a boolean.
I did that more than once. Not because I didn't understand Redux. Because I didn't have a real test for when not to reach for it, so I defaulted to the tool that felt "serious."
The habit nobody tells you to unlearn
Early in my React years, the advice I absorbed from tutorials was basically: small app, use useState. Big app, use Redux. That's a size-based rule, and size is a terrible proxy for what actually matters, which is how the state behaves, not how many files your project has.
A five-screen internal tool with a workflow editor, undo history, and three roles fighting over permissions? That probably wants Redux. A forty-page marketing site with a theme toggle and a mobile nav state? That's Context, maybe not even that.
I learned this the slow way, on a dashboard project where I'd wired notification counts, a selected-workspace value, and a live job feed all into one AppContext. It worked fine in the demo. Then someone added a fourth thing to that context, a component three levels down started re-rendering on every WebSocket tick, and I spent an afternoon staring at React DevTools trying to figure out why a settings icon was blinking every two seconds like it had a heartbeat.
What actually separates the two tools
Here's the reframe that fixed it for me, and it's less "which library" and more "what job is this state doing."
Context is a plumbing solution. It moves a value down the tree without prop drilling. That's it. It doesn't care where the value came from or how it changes — you could feed it a useState, a useReducer, or a hardcoded object, and Context wouldn't know the difference.
Redux is an architecture. Actions, reducers, a single store, a predictable flow of "this happened → here's the new state." You get that structure whether you need it or not, and for small, cohesive values, that structure is overhead, not help.
So the actual decision isn't Context vs Redux. It's closer to:
useState / useReducer + Context
vs.
Redux Toolkit + selectors
Once I started asking "does this state need an architecture, or does it just need to reach three components without fifteen props," most of my choices got obvious fast.
A quick gut-check I actually use now
Three questions, roughly in this order:
- Who owns this? One feature tree, or does it get read/written across totally unrelated routes?
- How does it change? A handful of clean transitions, or a tangle of business rules that touch five other pieces of state?
- Who's subscribing? Do consumers basically all need "the same thing," or does one tiny badge need to react to a firehose of updates without the rest of the screen re-rendering?
If the answers land on "one feature, simple transitions, everyone wants the same value" — Context, and honestly probably just useReducer + Context, no extra library required.
If it's "read everywhere, complicated rules, and only slivers of it change independently" — that's where Redux Toolkit's selector model starts paying for its setup cost.
The mistake I don't make anymore
I used to think memoizing everything in a context provider was the fix for re-render pain. useMemo on the value, useCallback on every handler, wrap, wrap, wrap. It helps with accidental identity changes — a new object reference on every render for no reason — but it can't give you the thing Redux's useSelector gives you for free: a component subscribing to one slice of a much bigger state tree.
Memoization fixes noise. It doesn't fix architecture. If you're three layers of useMemo deep just to stop unrelated components from updating, that's usually a sign the state itself is trying to tell you something — probably that it's outgrown Context, or that it was never one cohesive value in the first place and needs splitting into separate providers.
That second one, by the way, is underrated as a fix. Before reaching for Redux, try just... splitting the context. A ThemeContext and a NotificationsContext and a WorkspaceContext instead of one AppContext solves a surprising number of "why is everything re-rendering" problems on its own.
Where I landed
These days my default order looks something like: local state first, always — don't be the person adding global anything for two props. Context next, for stuff that's genuinely shared but genuinely simple. Redux Toolkit only once I can point at real coordination complexity — multiple features reacting to the same event, selectors doing real work, or a debugging need that "just add a console.log" won't satisfy anymore.
And API data doesn't go in either bucket, honestly. That's a separate problem — caching, refetching, staleness — that neither Context nor a hand-rolled Redux slice handles well without a lot of extra plumbing you're better off not building yourself.
I went looking for a writeup that laid out the decision framework more rigorously than my "vibes and a bad afternoon" version, and found a genuinely thorough one — includes a full decision framework, side-by-side code for both approaches, and a section specifically on the failure modes (the universal-context trap, memoizing a design problem instead of fixing it, stuffing every form input into Redux) that I definitely lived through firsthand. Worth the read if you're mid-decision on a real feature right now: React Context API vs Redux: When to Use Each.
If you've got your own "I reached for the wrong tool and paid for it" story, I'd genuinely like to hear it in the comments. Misery loves company, and also I collect these for the next time I'm tempted to npm install my way out of a design decision.
Top comments (0)