Zustand State Management
Manage global React state with Zustand - simple stores, actions, selectors, middleware, devtools, and persistence without boilerplate.
skill: Zustand State Management category: Web Development tags: [Zustand, State Management, React, Flux, JavaScript] readingTime: 5
Zustand is the sweet spot between useState (too local) and Redux (too much ceremony) for global React state. Its API is minimal - one hook, one store, no providers. Use it for UI state shared across the app; use React Query for server state.
Workflow
- Create a simple storeZustand stores are plain JavaScript objects with state and actions - no reducers, dispatchers, or action creators.
- Subscribe to specific slicesComponents that subscribe to the entire store re-render on any change. Selectors scope the subscription to only the relevant slice.
- Add devtools and persist middlewareDevtools integrates with Redux DevTools extension for time-travel debugging. Persist saves store to localStorage across reloads.
- Split large stores into slicesA single giant store becomes hard to maintain. Slices are independent pieces composed into one store.
- Update nested state immutablyZustand requires immutable updates - mutating state directly won't trigger re-renders. Use Immer middleware for deep nested updates.
Output Format
Zustand store: typed state + actions, selectors for performance, devtools + persist middleware, slices for modularity.
const useStore = create<State>()(devtools(persist(
(set, get) => ({
state: initialValue,
action: (arg) => set(prev => ({ ...prev, field: newValue })),
}),
{ name: 'store-key' }
)));Examples
Input
Output
Edge Cases & Gotchas
- Hydration mismatch with persist - persist causes SSR hydration mismatches (server state vs. localStorage). Use the skipHydration option and manually rehydrate on mount.
- Stale actions in callbacks - Actions from useStore() are stable - they don't change between renders. You can safely include them in useEffect dependency arrays.
- Computed values - Zustand has no built-in computed/derived values. Compute them in selectors: const count = useStore(s => s.items.length); or use a library like zustand-computed.
- Cross-store access - Stores can't directly access each other's state via hooks (can't call hooks outside components). Use getState() from the other store: useOtherStore.getState().value.
When to Break These Rules
- Use Jotai for atomic state (fine-grained per-atom subscriptions) when you have many independent pieces of state that need to be composed.
- Use Redux Toolkit for large enterprise apps with time-travel debugging requirements, complex middleware (sagas), or team standardization needs.
Reference Files
https://zustand.docs.pmnd.rs/https://github.com/pmndrs/zustand