Most React apps I audit reach for a global store before they have a problem it solves. Redux gets installed on day one, out of habit, and then half the app’s state is routed through it for no reason. Before you add a store, it is worth asking what state you actually have.
The habit
Somewhere along the way, “React app” started to imply “React plus a state library.” So the store goes in first, and every new piece of data gets pushed into it because that is where data goes now. The app ends up with a giant global object holding things that only one component ever reads.
That is not a store solving a problem. That is a store creating one.
What you are actually storing
Most of what people put in a global store falls into a few buckets.
- Local UI state: is this dropdown open, what is in this input.
- Server state: the user, the list of orders, the thing you fetched from an API.
- Genuinely global client state: theme, the logged-in user’s identity, a cart.
Only that last bucket wants a global store. The first belongs in the component. The second belongs somewhere else entirely.
Server state is not app state
The biggest chunk of what ends up in Redux is data that came from an API. That data has caching, refetching, staleness, and loading states as real concerns. A general purpose store handles none of that for you, so you hand-write it, badly.
A server-state library (React Query, or SWR) already solves caching, background refetch, and stale handling. Move your fetched data there and a huge share of your “global state” disappears. What is left is small.
When a store earns its place
There is a real line. You want a dedicated client store when you have state that is truly global, changes often, and is read by many unrelated parts of the tree. A complex cart, a collaborative editor’s local document, a multi-step wizard whose steps live far apart.
At that point a store is the right tool. Add it then, when the pain is concrete, not on day one as insurance.
The lighter path
Even when you do want shared client state, the library does not have to be heavy. On smaller apps I reach for Preact with signals. A signal is a single reactive value you read anywhere and update anywhere, with no reducers, no actions, no provider tree wrapping your whole app.
The rule I follow: local state until it hurts, server-state library for anything fetched, and a real store only for the slice that is genuinely global. Add the weight when you feel the need for it, not before.