Kinds of state and how to choose
Local, server, URL, form, global — each with its own tool.
Updated
What state management is
State management = the decisions about where you keep the data that changes in the app, who can read it and how it gets updated. In a small app, useState is enough. When several components, far from each other, need the same data, the question comes up: where do I put it?
The most important idea in this lesson: there isn't a single "state". There are several kinds, and each has its own tool. The classic mistake is to stuff everything into one global store.
The kinds of state
| Kind | Examples | The right tool |
|---|---|---|
| local UI | an open modal, the active tab, an input | useState, useReducer |
| server state | users, products, orders | Server Components, TanStack Query, RTK Query |
| URL | filters, page, sorting, search | searchParams, useSearchParams |
| form | values, errors, pending | <form> + useActionState, React Hook Form |
| global client | a cart in an SPA, preferences, the logged-in user in the UI | Context, Zustand, Redux Toolkit |
| persistent local | the theme, drafts | localStorage + a store |
How to choose — the questions, in order
Ask the questions in order and stop at the first "yes":
- Does it come from the server? → server state (Server Components, TanStack Query, RTK Query).
- Does it need to be shareable as a link? → the URL.
- Does a single component use it? →
useState. - Do a few nearby components use it? → lift the state into their common parent.
- Do many components use it, but it rarely changes? → Context.
- Is it global and does it change often? → Zustand or Redux Toolkit.
Context — what it is and what it isn't
Context passes a value down the tree without props at every level. It isn't a state manager — just a transport mechanism.
| Good for | Problematic for |
|---|---|
| the theme, the language, the logged-in user — rarely changing values | frequently changing data: every change re-renders every consumer |
| dependency injection (an API client, config) | big state with many independent fields |
Global state libraries — a comparison
| Context + useReducer | Zustand | Redux Toolkit | Jotai | |
|---|---|---|---|---|
| Model | a value in the tree | one store, a hook with a selector | one store, slices, actions | small atoms |
| Boilerplate | small | very small | medium | small |
| Re-renders | every consumer | only what you select | only what you select | only the atoms used |
| DevTools, time-travel | no | optional | excellent | optional |
| Server state included | no | no | yes — RTK Query | no |
| When | rare values | the default for most apps | big teams, complex logic, existing Redux code | granular, derived state |
What changed with the Next.js App Router
With Server Components, much of what used to live in Redux (product lists, the user's profile) is read directly on the server. Global client state becomes much smaller — often just UI (an open sidebar, a cart, preferences). That's why Zustand and even Context are often enough.
Summary
- There are several kinds of state; each has its own tool.
- Server state → a server cache, not a global store; filters → the URL.
- Context = transport for rare values; Zustand by default for global state; Redux Toolkit for big apps.