State Management

Keep server state, client state, and form state responsibilities explicit.

Use different tools for different state classes instead of one global pattern.

  • Server state: TanStack Query (fetching, caching, invalidation, background refresh).
  • Local UI/app state: Zustand for shared client state with clear ownership.
  • Form state: keep form-local state in form libraries/components unless globally required.

Decision rules

  • If data comes from backend and needs caching or refetching, treat it as server state.
  • If state coordinates UI across multiple components/routes, treat it as client app state.
  • If state is short-lived and tied to a single form, keep it local.

Implementation checklist

  • Define state ownership per module (server, client, form).
  • Keep query keys and API contracts centralized in api/ modules.
  • Avoid duplicating server state into Zustand stores unless there is a clear reason.
  • Add explicit invalidation rules for mutations.
  • Keep derived UI state close to the consuming module.

Common pitfalls

  • Storing remote server data in client stores by default.
  • Creating global stores for one-screen interactions.
  • Mixing async fetching concerns into presentational components.

On this page