FrontendJun 20263 min read

Local State vs State Management — You're Probably Asking the Wrong Question

Most 'I need a state management library' problems are actually server-state problems. Reach for useState first, React Query second, and a global store almost never. Here's how to tell which one you actually have.

🧊Nice Pick

Local state (plus React Query for server state)

The local-vs-global framing is a trap. The real split is client state vs server state. Keep client state local with useState/useReducer, hand server state to React Query, and you'll delete most of the reasons people install Redux or Zustand in the first place.

The Question Almost Everyone Gets Wrong

Search "local state vs state management" and you'll get ten articles saying the same thing: use useState for component data, reach for Redux or Context when you need to share. It's not wrong. It's just answering the wrong question.

The split that actually matters is client state vs server state. Client state is yours — a toggle, a form field, which tab is open. Server state is a cache of data that lives in a database somewhere else, and you're borrowing it. The reason people drown in "state management" is that they shove server state into a client-state tool (Redux, Zustand, Context) and then hand-write all the caching, refetching, and invalidation that tool was never built to do.

What Local State Is Actually For

Local state — useState, useReducer — is the correct default for anything a single component owns. Form inputs, open/closed toggles, the active tab, a hover flag. Most state in a real app is this. It needs no library, no provider, no boilerplate, and it's the fastest thing React does.

The modern best practice is genuinely just restraint: don't lift state until two components actually need it, and don't install anything until useState has visibly failed you. The vast majority of apps never reach that point.

The Server-State Trap (Where the Pain Actually Comes From)

Here's the move that quietly ruins codebases: you fetch a user, a list, an order — and you store the result in Redux or a Zustand store "so other components can use it." Now you own a cache. You're writing loading flags, error flags, refetch-on-focus, stale-after-mutation invalidation, and pagination glue by hand, in a tool that thinks your server data is just another piece of UI state.

This is what people mean when they say state management is hard. It isn't hard — they're solving a caching problem with a non-caching tool. React Query (TanStack Query) or SWR does all of it for you: caching, dedup, background refetch, mutation invalidation, out of the box. Move server state there and watch most of your "global store" evaporate.

So When Do You Actually Need a Global Store?

After you've kept client state local and moved server state to React Query, what's left that needs a real global store? Genuinely-shared client state: the current theme, auth/session info, a multi-step wizard's draft, a complex cross-tree UI mode. That's a short list.

For that short list, reach for Zustand — it's ~1.5KB, no provider, no boilerplate, and it does exactly one job well. Redux still has a place on large teams that want strict, auditable, time-travel-debuggable state conventions — but for most apps in 2026 it's solving a discipline problem, not a technical one.

The Decision, Compressed

1. Component owns it?useState / useReducer. Don't think twice. 2. It came from your server? → React Query or SWR. Not a store. 3. Genuinely-shared client state across the tree? → Zustand. 4. Big team needing strict, auditable conventions? → Redux Toolkit.

If you do the first two honestly, you'll be shocked how rarely you reach the second two. The teams buried in 'state management' almost always skipped straight to step 4 for problems that were really steps 1 and 2.

What Most Comparisons Get Wrong

They present this as a ladder — local state for small apps, a library for 'serious' ones — implying maturity means adopting a store. That's backwards. A mature React codebase uses less global state, not more, because it correctly routes server data to a query cache and keeps the rest local. Reaching for Redux on day one isn't sophistication. It's answering the wrong question with the heaviest possible tool.

Quick Comparison

FactorLocal StateState Management
Best forComponent-owned UI state (forms, toggles, tabs)Genuinely cross-tree shared client state
Setup costZero — built into ReactLibrary + store wiring (Zustand low, Redux high)
Server dataLeave it to React Query / SWR (not local, not a store)Common trap — stores cache server data badly
BoilerplateNoneMinimal (Zustand) → heavy (Redux)
Re-render controlManual — lifting/memoization as it growsFine-grained subscriptions (Zustand) avoid wasted renders
Scales to large teamsConventions drift without structureRedux gives strict, auditable, debuggable patterns
Bundle impact0KB~1.5KB (Zustand) → larger (Redux Toolkit)
The real questionIs this client state? Keep it local.Is this server state? Then neither — use a query cache.

The Verdict

Use Local State if: You're starting from scratch or untangling a mess: default to useState/useReducer for client state and React Query for server state. This deletes most of the demand for a global store.

Use State Management if: After doing that, you still have genuinely-shared client state across the tree (theme, session, wizard draft) — reach for Zustand; reach for Redux Toolkit only if a large team needs strict, auditable conventions.

Consider: React Query (TanStack Query) or SWR — the missing third option most 'local vs state management' debates ignore, and the actual fix for the pain that sends people library-shopping.

🧊
The Bottom Line
Local state (plus React Query for server state) wins

The local-vs-global framing is a trap. The real split is client state vs server state. Keep client state local with useState/useReducer, hand server state to React Query, and you'll delete most of the reasons people install Redux or Zustand in the first place.

Related Comparisons

Disagree? nice@nicepick.dev