The state management landscape has consolidated significantly. The “Redux for everything” era is over; modern apps use specialized tools per state category. Senior frontend interviews probe whether you understand the categories and the right tool for each.
The five categories of state
- Server state: data fetched from APIs (lists, items, user info)
- Client state: UI state (selected tab, expanded item)
- Form state: what the user is currently typing/selecting
- URL state: route, query params, hash
- Computed state: derived from other state
Different tools for different categories. Mixing them causes pain.
Server state: TanStack Query / SWR / Apollo
Built specifically for the lifecycle of data fetched from servers:
- Caching with TTLs
- Background refetching
- Stale-while-revalidate
- Mutations with optimistic updates
- Pagination, infinite scroll
- Retry logic
TanStack Query (formerly React Query) is the dominant choice in 2026. SWR is simpler. Apollo is for GraphQL specifically.
Client state: Zustand / Jotai / Redux Toolkit
For state that is yours, not the server’s:
Zustand
Tiny (~3KB), simple. Single store with hooks. The default for small-to-medium apps.
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));
Jotai
Atomic state. Each piece of state is an atom. Components subscribe to specific atoms; only re-render when those change.
Best for fine-grained state with many disjoint slices.
Redux Toolkit (RTK)
Modern Redux. Less boilerplate than classic Redux. Best for large apps with complex state interactions, time-travel debugging needs, or strict structural patterns.
Adoption is declining for new projects but RTK remains in many large codebases.
Form state: React Hook Form / Formik
Forms have unique requirements (validation, dirty tracking, submission). General state libraries do not handle them well.
React Hook Form is the modern default. Formik still in use; new projects pick RHF.
URL state: nuqs / route params
Search params (filters, page numbers, selected items) often belong in the URL — sharable, browser-back-friendly, indexable.
nuqs (or similar) provides typed query-param management without manual parsing/encoding.
The Redux trap
Older codebases store everything in Redux: server data, form state, UI state. This produces:
- Boilerplate without benefit
- Re-renders on every state change
- Confused mental model — what kind of state is this?
Modern best practice: server state in TanStack Query, client state in Zustand or Context, form state in RHF, URL state in route library. Redux only where genuinely needed.
Context: when and when not
React’s built-in useContext is fine for:
- Theme
- Locale
- Authentication state (current user)
Not great for:
- Frequently updated state — every consumer re-renders on every change
- Complex selectors
- State that needs persistence
Selectors and memoization
For derived state from a store, use selectors:
const visibleTodos = useStore(s => s.todos.filter(t => !t.done));
Without memoization, the component re-renders on every store change, even unrelated ones. Most modern stores (Zustand, Redux Toolkit) handle selector memoization for you.
State that survives reload
Persistence options:
- localStorage / sessionStorage — for non-sensitive client state
- IndexedDB — for larger datasets
- Server — for anything that should survive across devices
Zustand has a persist middleware. Redux has redux-persist. Both reasonably reliable for the common cases.
Common mistakes
- Using Redux for server data instead of TanStack Query
- useState + useEffect to fetch data (manual implementation of what libraries handle)
- Putting too much in Context (re-render storm)
- Storing form state at the global level (should be local)
- Storing URL state somewhere other than the URL (loses shareability)
Frequently Asked Questions
What about Recoil?
Effectively dead. Replaced by Jotai for atomic state needs.
Should I use Server Components instead of state management?
For server-rendered content, yes — RSC reduces client-side state needs significantly. Interactive parts still need client state.
How do I structure a Zustand store for a large app?
Slice the store into independent stores by domain (user, cart, ui). Compose where needed. Keep each store small.