Skip to the content.

When you don't need Redux

When you don't need Redux

Redux solves a specific problem: a lot of client state, changed from many places, that many parts of the app must read. When that is your problem, Redux’s ceremony — actions, reducers, a store, selectors — is worth it, because the alternative is worse. But teams reach for it reflexively, on apps that have none of those three properties, and then resent the boilerplate. The honest question is not “should I use Redux?” but “do I actually have the problem Redux solves?” Most of the time the answer points at a smaller tool, and choosing it is not cutting corners — it is matching the tool to the shape of the state.

A decision tree from the kind of state to the right tool Is it server data? use a query cache. Is it local to one component? use useState. Is it shared but rarely changing? use context. Is it a lot of client state changed from many places? use Redux. what kind of state? server data→ query cache one component→ useState shared, rarely changes→ context lots, many writers→ Redux
Sort the state by its kind first. Only the bottom-right branch — a lot of client state with many writers — is the one Redux was built for.

Local state stays local

If a piece of state is used by one component and its children, it belongs in that component. Reaching for a global store to hold a modal’s open/closed flag or a form’s draft value just adds indirection for something useState handles in a line:

function SearchBox() {
  const [query, setQuery] = useState("");   // nobody else needs this — keep it here
  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}

Colocate first; lift state up only when a second component genuinely needs it.

Server data is not client state

The biggest category people wrongly put in Redux is data that lives on the server. Cached remote data has needs a store does not give you for free — caching, revalidation, deduping, background refetch — and hand-rolling those in reducers is a lot of code that a query library already ships:

// a query cache handles loading, caching, and refetch — no reducer needed
const { data, isLoading, error } = useQuery(["user", id], () => fetchUser(id));

If most of your “global state” is really API responses, a query cache plus local component state often removes the need for Redux entirely.

Shared-but-simple has lighter tools too

For genuinely shared client state that changes rarely — a theme, the current user, a locale — React Context is enough; you do not need reducers and middleware for a value that flips occasionally. Redux earns its keep when the shared client state is substantial and changed from many places: a collaborative editor’s document, a complex multi-step form’s cross-cutting state, a cart touched by dozens of actions. There the disciplined action log, the DevTools time-travel, and the single source of truth stop being ceremony and start being the thing that keeps a hard problem tractable. The rule of thumb: start with the smallest tool that fits, and adopt Redux when you feel the absence of its structure, not before. The simple-store exercise builds the store loop by hand so you can see exactly what Redux gives you — and therefore when you actually need it.