React State Management in Depth #6: Which Tool, When — a Decision Guide

5 min read

This is the final post of the series. In #1 we split state into client state and server state, and from #2 to #5 we looked at the tools one by one. This post ties all of it into a single decision flow. The goal is to give you a sequence of questions to ask yourself when building a new screen.

The very first question #

Let me revisit the conclusion of #1. Whatever state you encounter, the first question is one.

“Is this value self-contained in the browser, or a copy of server data?”

This single question settles half of it.

  • If it’s a copy of server data → it’s server state. Don’t put it in a global store; leave it to TanStack Query.
  • If it’s self-contained in the browser → it’s client state. Move on to the next question.

The decision flow in one chart #

State management decision flow
What is this value?
├─ A copy of server data (lists, details, search results)
│     → TanStack Query (#2)
│        leave caching / refetch / freshness to the tool
└─ Client state, self-contained in the browser
      ├─ Used in one component (or a direct child) only
      │     → useState / useReducer (built-in)
      ├─ Shared across components but rarely changes
      │   (theme, locale, logged-in user info, etc.)
      │     → useContext is enough
      └─ Shared across components and changes often
            ├─ Lightly, with minimal boilerplate
            │     → Zustand (#3)  ── one store + selectors
            │     → Jotai (#4)    ── small atoms + derived atoms
            └─ Large team / strict change conventions / strong traceability
               or an existing Redux codebase
                  → Redux Toolkit (#5)

Don’t skip past the built-ins #

We looked at five tools, but the one you’ll use most is still useState. A new library only earns its place when the conditions shared across components + far apart in the tree stack up. Before that, the built-ins are the simplest and the fastest.

Adding a single tool is itself a cost: bundle size, learning curve, and a higher barrier to entry for new teammates. The habit of first doubting “does this state really need a global store” leads to good design.

Zustand vs Jotai — how to split the decision #

Both are light and Provider-free, so they get compared directly often. Here’s the difference we saw in #3 and #4, in one table.

CriterionZustandJotai
Modeltop-down — one storebottom-up — small atoms assembled
State it fitsglobal state bound into one lumpstate with many interdependent derived values
Re-render controlpartial subscription via selectorsnaturally separated per atom
Feel of use“keep one global store”“use useState split out globally”

There’s no single right answer. If state naturally binds into one lump, Zustand is cleaner; if values reference each other and derive, Jotai is cleaner. Picking the one your team is familiar with is a perfectly valid criterion too.

Five common traps #

Here are the mistakes I emphasized repeatedly throughout the series.

1. Putting server data in a global client store. The most common and costly mistake. You end up rebuilding caching and refetching by hand. Leave server state to TanStack Query (or RTK Query).

2. Cramming all state into one giant global object. A change in one place easily re-renders unrelated components too. Break state up per selector or per atom to narrow subscriptions.

3. Lifting even local state to global. One form input, one toggle is plenty with useState. Go global only when sharing is genuinely needed.

4. Mixing too many tools. Put Redux, Zustand, and Jotai all in one app, and a new teammate has to hunt down “where does this state live” every time. It’s better to settle on a single client-state tool.

5. Guessing about re-renders without measuring. Before swapping tools “because it feels slow,” check the actual re-renders with the React DevTools Profiler. The problem is usually not the tool but the subscription scope.

A realistic combination #

The stable combination you see often in practice is simple.

A common real-world combination
Server state    → TanStack Query
Client state    → one of Zustand or Jotai
Local state     → useState / useReducer
Occasional static global → useContext (theme, locale, etc.)

Before adding more tools, these four slots neatly organize most apps. The key is to first distinguish the kind of state, and place each kind in its proper slot.

Closing the series #

The message running through all six parts is the same as it was at the start. Before memorizing tools, first distinguish the nature of your state.

  • There is client state and server state, and they fit different tools. (#1)
  • Leave server state to TanStack Query to automate caching and refetching. (#2)
  • Lightweight global client state suits Zustand or Jotai. (#3, #4)
  • Redux Toolkit still plays its part in large teams and existing codebases. (#5)

State management isn’t a field where you memorize the right answer; it’s one where you read the situation and pick the fitting tool. If you ask “what kind of state is this value” first whenever you work on a new screen, which tool to reach for follows naturally.

That concludes the “React State Management in Depth” series. For a deeper topic, I recommend following up with how client state and server state divide in the Server Components environment of the Modern React + Next.js series.

X