{"filename":"react-zustand-cursorrules-prompt-file.mdc","content":"---\ndescription: \"React and TypeScript state management guidance for Zustand stores, selectors, middleware, persistence, and testing.\"\nglobs: **/*.ts, **/*.tsx\nalwaysApply: false\n---\nYou are an expert in React, TypeScript, and Zustand state management.\n\n# React + Zustand Guidelines\n\n## State Ownership\n- Keep ephemeral UI state in the nearest component with `useState` or `useReducer`.\n- Use URL state for shareable filters, pagination, tabs, and search params.\n- Use Zustand only for client state that is genuinely shared across unrelated components.\n- Use TanStack Query, SWR, RTK Query, or the existing project data layer for server state.\n- Never duplicate fetched server data into a Zustand store unless there is a documented offline or draft-editing requirement.\n\n## Store Design\n- Model each store as state plus named actions; avoid exposing anonymous setters for component code to misuse.\n- Keep stores small and domain-focused: auth session view state, command palette state, cart draft state, editor state, etc.\n- Split large stores into typed slices, then apply middleware only at the composed store boundary.\n- Keep derived values as selectors or small pure helpers unless they must be cached in state.\n- Store serializable data by default; keep DOM nodes, promises, sockets, and timers outside store state.\n\n```ts\nimport { create } from 'zustand'\n\ninterface SidebarState {\n  isOpen: boolean\n  activePanelId: string | null\n}\n\ninterface SidebarActions {\n  openPanel: (panelId: string) => void\n  close: () => void\n  toggle: () => void\n}\n\ntype SidebarStore = SidebarState & SidebarActions\n\nexport const useSidebarStore = create<SidebarStore>()((set) => ({\n  isOpen: false,\n  activePanelId: null,\n  openPanel: (panelId) => set({ isOpen: true, activePanelId: panelId }),\n  close: () => set({ isOpen: false, activePanelId: null }),\n  toggle: () => set((state) => ({ isOpen: !state.isOpen })),\n}))\n```\n\n## Component Usage\n- Subscribe to the smallest possible slice: `useStore((state) => state.value)`.\n- Do not call a store hook without a selector in components unless the component truly needs every field.\n- Select actions separately or through a shallow selector when grouping them.\n- Use `useShallow` for object or tuple selectors that return multiple values.\n- Keep selectors pure and cheap; move expensive derivations into memoized helpers if needed.\n\n```tsx\nimport { useShallow } from 'zustand/react/shallow'\nimport { useSidebarStore } from '@/stores/sidebar-store'\n\nexport function SidebarToggle() {\n  const { isOpen, toggle } = useSidebarStore(\n    useShallow((state) => ({\n      isOpen: state.isOpen,\n      toggle: state.toggle,\n    })),\n  )\n\n  return (\n    <button type=\"button\" aria-expanded={isOpen} onClick={toggle}>\n      Toggle sidebar\n    </button>\n  )\n}\n```\n\n## TypeScript\n- Define explicit state and action interfaces for shared stores.\n- Avoid `any`; use `unknown` plus narrowing for external data.\n- Type action payloads and return values, including async actions.\n- Prefer discriminated unions for complex local status instead of several loosely related booleans.\n- Export store state types when tests, utilities, or vanilla store factories need them.\n\n## Updates and Middleware\n- Use functional `set((state) => nextState)` when the next value depends on current state.\n- Treat nested state immutably; install and use `immer` middleware only when it materially simplifies nested updates.\n- Use `persist` only for state that must survive reloads.\n- Use `partialize`, `version`, and `migrate` when persisting anything beyond trivial preferences.\n- Never persist secrets, access tokens, refresh tokens, raw PII, or long-lived authorization state to browser storage.\n- Use `devtools` in development for complex flows and give important actions clear names.\n- Use `subscribeWithSelector` for non-React subscriptions that need fine-grained updates.\n\n## Async Actions\n- Async store actions may coordinate client-only workflows, optimistic drafts, or local device APIs.\n- Keep HTTP fetching in the project's server-state layer unless the state is explicitly client-owned.\n- Represent async client workflows with explicit statuses such as `idle`, `pending`, `success`, and `error`.\n- Reset error state deliberately when retrying or closing a workflow.\n\n## SSR and React Server Components\n- Do not read or mutate browser-only stores from React Server Components.\n- In SSR frameworks, create per-request vanilla stores when state must be initialized on the server.\n- Guard persisted stores against hydration mismatches before rendering storage-backed values.\n- Keep store modules free of direct `window`, `document`, and storage access outside middleware configuration.\n\n## Testing\n- Test store actions directly without rendering React when possible.\n- Reset stores between tests with their initial state.\n- Assert selectors and actions separately from component behavior.\n- Mock server-state libraries instead of routing fetched data through Zustand for tests.\n\n## Anti-Patterns\n- Do not create one global store for the entire application.\n- Do not put form input state in Zustand unless multiple distant components edit the same draft.\n- Do not mutate nested objects directly without Immer middleware.\n- Do not use Zustand as an event bus; prefer explicit callbacks, services, or a scoped store.\n- Do not introduce Redux-style reducers, action constants, or dispatch wrappers unless the project already uses that pattern.\n"}