# Client State Management > Decide where UI state lives; pick the right tool for the scope. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/client-state-management Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary Client state is data that exists only in the browser: UI toggles, form drafts, selection, transient layout. The decision tree is local → lifted → global. useState and useReducer cover most cases; context gates renders to a subtree; external stores (Zustand, Redux Toolkit, Jotai) step in when state fan-out or update frequency outgrows React's own diffing. ## Definition Client state is anything the browser owns and the server does not need to persist. It sits on a spectrum by scope: local (one component, useState/useReducer), lifted (siblings share it via a common ancestor), and global/cross-cutting (auth, theme, cart — lives in a store or context). The key rule: server-fetched data is not client state. Data from an API belongs in a cache layer (React Query, SWR, RTK Query), not in useState — putting it there duplicates the truth and creates sync bugs. ## Choosing a client-state tool | Tool | Best fit | Re-render model | Bundle cost | | --- | --- | --- | --- | | **useState / useReducer** | Local or lifted state | Owning component only | Zero — built-in | | **Context + useContext** | Rarely-changing subtree value | Every consumer on any reference change | Zero — built-in | | **Zustand (create)** | App-wide UI state, moderate complexity | Selector-scoped — only chosen slice | ~3 kB gzipped | | **Redux Toolkit (createSlice)** | Large team, auditable action log | Selector-scoped via useSelector | ~16 kB gzipped | | **Jotai (atom)** | Scattered feature-local atoms | Per-atom — unused atoms tree-shaken | ~3 kB gzipped | ## Scope decision rules - Start local: useState owns one value; useReducer owns complex shape or multi-step transitions. - Lift before you globalise: two sibling components sharing state go to their nearest common ancestor. - Context works for low-frequency shared values (theme, locale); split contexts by update frequency to limit re-renders. - A Zustand selector (useStore(s => s.count)) re-renders only when count changes — use it over bare useStore(). - Jotai atoms are co-located with features and tree-shaken automatically; prefer it when state is scattered across many features. ## Zustand store with typed selectors A minimal cart store using Zustand's create factory. The selector on the last line means the component re-renders only when totalItems changes — unrelated store mutations are skipped. **Zustand cart store** ```ts import { create } from "zustand"; interface CartState { items: { id: string; qty: number }[]; addItem: (id: string) => void; totalItems: () => number; } export const useCartStore = create()((set, get) => ({ items: [], addItem: (id) => set((s) => { const existing = s.items.find((i) => i.id === id); if (existing) { return { items: s.items.map((i) => i.id === id ? { ...i, qty: i.qty + 1 } : i, ), }; } return { items: [...s.items, { id, qty: 1 }] }; }), totalItems: () => get().items.reduce((sum, i) => sum + i.qty, 0), })); // In a component — re-renders only when the total quantity changes const count = useCartStore((s) => s.totalItems()); ``` The selector `s => s.totalItems()` means this component skips re-renders when unrelated store slices change. No provider needed. ## Watch out: Don't put server data in a client store Server-fetched data needs background refetch, deduplication, and cache invalidation — none of which useState or a Zustand store provides. Storing API responses in client state creates silent stale-data bugs. Use React Query, SWR, or RTK Query instead. The same rule applies to any value whose source of truth lives on the server. ## Key terms - **useState**: React hook for a single local state value; re-renders the owning component on change. - **useReducer**: React hook managing state via a pure `(state, action) => state` function. - **Context**: React mechanism to share a value to a subtree without prop-drilling; re-renders all consumers on change. - **Zustand `create`**: Factory that returns a typed store hook; selectors limit which components re-render. - **Jotai `atom`**: Smallest unit of Jotai state; atoms compose via derived atoms and are tree-shakeable. - **createSlice**: RTK function that generates action creators and a reducer from a single object. ## Related topics - [Server State & Data Fetching](https://fearchitect.com/topics/server-state-data-fetching.md): Async, shared, remote-owned data that requires a dedicated cache layer. - [Signals & Fine-Grained Reactivity](https://fearchitect.com/topics/signals-fine-grained-reactivity.md): Observable values that re-run only their exact dependents. - [Optimistic UI & Mutations](https://fearchitect.com/topics/optimistic-ui-mutations.md): Update the UI before the server replies; roll back on error. - [Render Performance](https://fearchitect.com/topics/render-performance-patterns.md): Skip renders, defer slow work, and virtualize long lists. - [Component Architecture & Project Structure](https://fearchitect.com/topics/component-architecture.md): Structuring components so composition beats configuration. - [REST vs GraphQL vs tRPC](https://fearchitect.com/topics/rest-vs-graphql-vs-trpc.md): Three API styles with distinct fetch, type, and caching trade-offs. ## Further reading - [Zustand docs](https://zustand.docs.pmnd.rs/) - [Redux Toolkit — createSlice API](https://redux-toolkit.js.org/api/createSlice) - [Jotai docs — core concepts](https://jotai.org/docs/core/atom) - [React docs — choosing the state structure](https://react.dev/learn/choosing-the-state-structure) - [React docs — passing data deeply with context](https://react.dev/learn/passing-data-deeply-with-context)