State Management
Zustand stores, server state with React Query, and caching patterns.
When to use what
| Layer | Tool | Examples |
|---|---|---|
| Server state | TanStack Query | API data, lists, detail views |
| Client UI state | Zustand | Sidebar, filters, wizard steps |
| Form state | React Hook Form | Multi-step forms, validation |
| URL state | Router search params | Pagination, active tab |
Zustand store pattern
import { create } from "zustand";
type UiState = {
sidebarOpen: boolean;
activeWorkspace?: string;
toggleSidebar: () => void;
setWorkspace: (id: string) => void;
reset: () => void;
};
export const useUiStore = create<UiState>((set) => ({
sidebarOpen: true,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
setWorkspace: (id) => set({ activeWorkspace: id }),
reset: () => set({ sidebarOpen: true, activeWorkspace: undefined }),
}));Server state with React Query
import { useQuery } from "@tanstack/react-query";
export function useProjects() {
return useQuery({
queryKey: ["projects"],
queryFn: () => apiFetch("/v1/projects"),
staleTime: 5 * 60 * 1000,
});
}Conventions
- One store file per domain (
stores/session.ts,stores/filters.ts) - Never put server-fetched data in Zustand — use React Query
- Reset stores on logout
- Avoid prop drilling — use stores or context at layout level