react zustand
via PatrickJS/awesome-cursorrules
React and TypeScript state management with Zustand: stores, selectors, middleware, and testing patterns.
What is react zustand?
Guidance for building scalable Zustand stores in React and TypeScript projects. Covers store design, component subscriptions, async workflows, persistence, SSR safety, and testing—with emphasis on keeping stores small, domain-focused, and free of server state duplication.
- Design domain-focused stores with explicit state and action interfaces, avoiding global monoliths
- Subscribe to minimal state slices in components using selectors and useShallow for efficiency
- Handle async client workflows with explicit status tracking (idle, pending, success, error)
- Apply middleware safely: persist only non-sensitive data, use immer for nested updates, enable devtools for debugging
- Test store actions directly and reset state between tests without rendering React
- Avoid anti-patterns: no form input state in stores, no event bus usage, no direct nested mutations without Immer
Applies to
File patterns this rule matches.
Rule definition (reference)
Source of truth, from the repository.
You are an expert in React, TypeScript, and Zustand state management.
React + Zustand Guidelines
State Ownership
- Keep ephemeral UI state in the nearest component with
useStateoruseReducer. - Use URL state for shareable filters, pagination, tabs, and search params.
- Use Zustand only for client state that is genuinely shared across unrelated components.
- Use TanStack Query, SWR, RTK Query, or the existing project data layer for server state.
- Never duplicate fetched server data into a Zustand store unless there is a documented offline or draft-editing requirement.
Store Design
- Model each store as state plus named actions; avoid exposing anonymous setters for component code to misuse.
- Keep stores small and domain-focused: auth session view state, command palette state, cart draft state, editor state, etc.
- Split large stores into typed slices, then apply middleware only at the composed store boundary.
- Keep derived values as selectors or small pure helpers unless they must be cached in state.
- Store serializable data by default; keep DOM nodes, promises, sockets, and timers outside store state.
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 })),
}))
Component Usage
- Subscribe to the smallest possible slice:
useStore((state) => state.value). - Do not call a store hook without a selector in components unless the component truly needs every field.
- Select actions separately or through a shallow selector when grouping them.
- Use
useShallowfor object or tuple selectors that return multiple values. - Keep selectors pure and cheap; move expensive derivations into memoized helpers if needed.
import { useShallow } from 'zustand/react/shallow'
import { useSidebarStore } from '@/stores/sidebar-store'
export function SidebarToggle() {
const { isOpen, toggle } = useSidebarStore(
useShallow((state) => ({
isOpen: state.isOpen,
toggle: state.toggle,
})),
)
return (
<button type="button" aria-expanded={isOpen} onClick={toggle}>
Toggle sidebar
</button>
)
}
TypeScript
- Define explicit state and action interfaces for shared stores.
- Avoid
any; useunknownplus narrowing for external data. - Type action payloads and return values, including async actions.
- Prefer discriminated unions for complex local status instead of several loosely related booleans.
- Export store state types when tests, utilities, or vanilla store factories need them.
Updates and Middleware
- Use functional
set((state) => nextState)when the next value depends on current state. - Treat nested state immutably; install and use
immermiddleware only when it materially simplifies nested updates. - Use
persistonly for state that must survive reloads. - Use
partialize,version, andmigratewhen persisting anything beyond trivial preferences. - Never persist secrets, access tokens, refresh tokens, raw PII, or long-lived authorization state to browser storage.
- Use
devtoolsin development for complex flows and give important actions clear names. - Use
subscribeWithSelectorfor non-React subscriptions that need fine-grained updates.
Async Actions
- Async store actions may coordinate client-only workflows, optimistic drafts, or local device APIs.
- Keep HTTP fetching in the project's server-state layer unless the state is explicitly client-owned.
- Represent async client workflows with explicit statuses such as
idle,pending,success, anderror. - Reset error state deliberately when retrying or closing a workflow.
SSR and React Server Components
- Do not read or mutate browser-only stores from React Server Components.
- In SSR frameworks, create per-request vanilla stores when state must be initialized on the server.
- Guard persisted stores against hydration mismatches before rendering storage-backed values.
- Keep store modules free of direct
window,document, and storage access outside middleware configuration.
Testing
- Test store actions directly without rendering React when possible.
- Reset stores between tests with their initial state.
- Assert selectors and actions separately from component behavior.
- Mock server-state libraries instead of routing fetched data through Zustand for tests.
Anti-Patterns
- Do not create one global store for the entire application.
- Do not put form input state in Zustand unless multiple distant components edit the same draft.
- Do not mutate nested objects directly without Immer middleware.
- Do not use Zustand as an event bus; prefer explicit callbacks, services, or a scoped store.
- Do not introduce Redux-style reducers, action constants, or dispatch wrappers unless the project already uses that pattern.
Related rules
Senior full-stack TypeScript, React, Node.js guidance with clean architecture, testing, and WHY-oriented reasoning.
Quantitative factor research skills for designing, evaluating, and mining alpha factors in equities markets.
Android development with Jetpack Compose, clean architecture, and Material Design 3.
Angular development with Novo Elements UI library using standalone components.
Expert Angular 18 + TypeScript development with Jest, emphasizing clean code and performance.
Manage Kubernetes clusters, add-ons, stacks, and credentials via the Ankra CLI platform.