cursor / claude rules
React Zustand Cursor Rules
Cursor rules for React + Zustand client state. Store ownership boundaries, selectors, middleware, persistence, and testing.
## what it does
tl;dr: paste this .mdc into .cursor/rules/ to keep Cursor from inventing global stores, mutating nested state, or stuffing server data into Zustand.
What the upstream rule covers
Sourced verbatim from PatrickJS/awesome-cursorrules → react-zustand-cursorrules-prompt-file.mdc. The rule is scoped to React + TypeScript + Zustand and is the canonical “don’t misuse Zustand” guardrail for that stack.
The state-ownership boundary the rule draws: useState/useReducer for ephemeral UI, URL state for shareable filters and pagination, Zustand only for client state genuinely shared across unrelated components. Server state goes to TanStack Query / SWR / RTK Query — never duplicated into a Zustand store “for convenience.”
What it tells Cursor
- Store design: state + named actions per store; small, domain-focused stores (
auth session,command palette,cart draft,editor); typed slices composed at the store boundary; serializable data only. - Component usage: smallest selector slice via
useStore((s) => s.value);useShallowfor object/tuple selectors; never call a store hook without a selector unless the component truly needs every field. - Updates and middleware: functional
set((state) => nextState);immermiddleware only when nested updates are materially simpler;persistonly for state that must survive reloads — never tokens, refresh tokens, raw PII, or long-lived auth. - TypeScript: explicit state and action interfaces; no
any; discriminated unions for complex local status; export store types when tests or vanilla factories need them. - SSR / RSC: never read or mutate browser-only stores from React Server Components; per-request vanilla stores for server-initialized state; guard persisted stores against hydration mismatches.
- Testing: test store actions directly without rendering React; reset to initial state between tests; mock server-state libraries instead of routing fetched data through Zustand.
- Anti-patterns the rule blocks: one global app store; form input state in Zustand unless multiple distant components edit the same draft; direct nested mutation without Immer; Zustand as an event bus; Redux-style reducers, action constants, and dispatch wrappers.
Install
mkdir -p .cursor/rules
curl -L -o .cursor/rules/react-zustand.mdc \
https://raw.githubusercontent.com/PatrickJS/awesome-cursorrules/main/rules/react-zustand-cursorrules-prompt-file.mdc
Minimal store shape the rule enforces
import { create } from "zustand"
interface SidebarState {
isOpen: boolean
activePanelId: string | null
}
interface SidebarActions {
openPanel: (panelId: string) => void
close: () => void
toggle: () => void
}
type SidebarStore = SidebarState & SidebarActions
export const useSidebarStore = create<SidebarStore>()((set) => ({
isOpen: false,
activePanelId: null,
openPanel: (panelId) => set({ isOpen: true, activePanelId: panelId }),
close: () => set({ isOpen: false, activePanelId: null }),
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}))
Gotchas
- The rule’s persistence guard is the load-bearing one: Cursor will happily persist whatever you put in
persistunless you tell it not to. Keep tokens and PII out of browser storage. useShallowis the rule’s answer to the “subscribed to the whole store, re-rendering on every change” footgun. Use it for any multi-field selector.- The
globs: **/*.ts, **/*.tsx, alwaysApply: falsefrontmatter in the upstream.mdcmeans Cursor applies this only when the rule is referenced or the file matches — paste it as-is, do not flipalwaysApplytotrueunless you want Zustand opinions on every CSS edit.
Related
- TanStack Query v5 Cursor Rules — server-state half of the same stack
- React Router v7 Cursor Rules — file-based routing conventions that pair with TanStack Query
- Svelte Cursor Rules — if you’re comparing stores across frameworks
- Cursor Rules Guide
## embed this badge
cache ✓ in cache.directory
