store
Skill4 starsChanged 2 months agoMITView on GitHub
Create a Zustand store for state management. Use when you need interactive client-side state that persists between components within a session. Use this skill for modal state, UI toggles, selection state, or any client-side state shared across components. For persistence across page refreshes, use the persist middleware pattern.
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 22
Read docs/FRONTEND.md for state management rules
- Distinctive: line 33
Use Zustand stores when:
- Distinctive: line 38
Don't: use stores for:
- Very specific: line 238
Use partialize to control which fields are persisted to localStorage.
- Distinctive: line 279
Only re-render when specific state changes:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
BEFORE IMPLEMENTING: Open
registry.yamland read:
patterns— allwhen: alwaysare mandatory; evaluate each conditional (when: <condition>) before codingbad_practices— keep these violations in mind throughout implementation
Create Zustand Store
Creates a Zustand store for managing interactive state following project patterns.
Platform applicability
This skill is single-flavor: react-only. Stores live in packages/app/react/src/stores/ (or a route's -stores/ folder for route-private state).
Not applicable to packages/app/astro/. Astro is render-time; if you find yourself needing a Zustand store on an astro page, the work probably belongs in the react app — link out to /app/... instead of replicating interactive state on the landing page.
Prerequisites
- Read
docs/FRONTEND.mdfor state management rules - Understand when to use stores vs URL state
When to Use Stores
| State Type | Solution | Example |
| URL/Shareable | Search Params | Filters, pagination, sorting |
| Interactive | Zustand Store | Form state, UI state, selections |
| Local | Props | Component-specific data |
Use Zustand stores when:
- State needs to persist across component mounts
- State is shared between multiple components
- State shouldn't be in the URL (e.g., modal open state)
Don't use stores for:
- Filter/search/pagination state (use URL)
- Data that should be shareable via link (use URL)
When NOT to Use Stores
- Server/API state — use TanStack Query
- URL state — use route search params (
Route.useSearch()) - Form state — use TanStack Form
- Single-component state — use
useState/useReducer
Store Types [STR-P05]
| Type | Location | Scope |
| Global | packages/app/react/src/stores/ | Entire app |
| Route | routes/[route]/-stores/ | Single route |
Account-level modes belong in a Global store, not the URL. A mode shared by every screen and fed to many SDK reads — e.g.
viewScope: SINGLE | MULTI(mono vs consolidated account view), selected org, role view — is global Zustand state undersrc/stores/(persisted), exported fromsrc/stores/index.ts. URL search params are for per-route, bookmarkable filters only (date range, page, productIds). A dashboard-scoped display preference (e.g. "discount additional costs") that several cards on one route read is a Route store (-stores/).
Process
Step 1: Create Store File
Global Store:
// packages/app/react/src/stores/useAuthStore.ts
import { create } from 'zustand'
interface AuthState {
user: User | null
token: string | null
isAuthenticated: boolean
}
interface AuthActions {
setUser: (user: User | null) => void
setToken: (token: string | null) => void
logout: () => void
}
type AuthStore = AuthState & AuthActions
export const useAuthStore = create<AuthStore>((set) => ({
// State
user: null,
token: null,
isAuthenticated: false,
// Actions
setUser: (user) => set({ user, isAuthenticated: !!user }),
setToken: (token) => set({ token }),
logout: () => set({ user: null, token: null, isAuthenticated: false }),
}))Route Store:
// packages/app/react/src/routes/products/-stores/useProductsStore.ts
import { create } from 'zustand'
interface ProductsState {
selectedIds: string[]
isDeleteModalOpen: boolean
viewMode: 'grid' | 'list'
}
interface ProductsActions {
toggleSelection: (id: string) => void
selectAll: (ids: string[]) => void
clearSelection: () => void
setDeleteModalOpen: (open: boolean) => void
setViewMode: (mode: 'grid' | 'list') => void
}
type ProductsStore = ProductsState & ProductsActions
export const useProductsStore = create<ProductsStore>((set) => ({
// State
selectedIds: [],
isDeleteModalOpen: false,
viewMode: 'grid',
// Actions
toggleSelection: (id) =>
set((state) => ({
selectedIds: state.selectedIds.includes(id)
? state.selectedIds.filter((i) => i !== id)
: [...state.selectedIds, id],
})),
selectAll: (ids) => set({ selectedIds: ids }),
clearSelection: () => set({ selectedIds: [] }),
setDeleteModalOpen: (open) => set({ isDeleteModalOpen: open }),
setViewMode: (mode) => set({ viewMode: mode }),
}))Step 2: Use in Components
import { useProductsStore } from '../-stores/useProductsStore'
function ProductsToolbar() {
const { selectedIds, clearSelection, setDeleteModalOpen, viewMode, setViewMode } =
useProductsStore()
return (
<div className="flex gap-2">
<span>{selectedIds.length} selected</span>
{selectedIds.length > 0 && (
<>
<Button onClick={() => setDeleteModalOpen(true)}>Delete Selected</Button>
<Button variant="outline" onClick={clearSelection}>Clear</Button>
</>
)}
<div className="ml-auto">
<Button
variant={viewMode === 'grid' ? 'default' : 'outline'}
onClick={() => setViewMode('grid')}
>
Grid
</Button>
<Button
variant={viewMode === 'list' ? 'default' : 'outline'}
onClick={() => setViewMode('list')}
>
List
</Button>
</div>
</div>
)
}Store Patterns [STR-C01, STR-C02, STR-C03, STR-P01, STR-P02, STR-P03]
With Computed Values
import { create } from 'zustand'
interface CartStore {
items: CartItem[]
addItem: (item: CartItem) => void
removeItem: (id: string) => void
// Computed (not stored, derived on access)
}
export const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
}))
// Computed selectors (outside store)
export const selectCartTotal = (state: CartStore) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
export const selectCartItemCount = (state: CartStore) =>
state.items.reduce((sum, item) => sum + item.quantity, 0)
// Usage
function CartSummary() {
const total = useCartStore(selectCartTotal)
const itemCount = useCartStore(selectCartItemCount)
// ...
}With Persistence
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useSettingsStore = create<SettingsStore>()(
persist(
(set) => ({
theme: 'light',
language: 'en',
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
}),
{
name: 'settings-storage', // localStorage key
}
)
)partialize — Restrict Persisted State
Use partialize to control which fields are persisted to localStorage. Only the returned fields are stored; the rest are ephemeral:
export const useSidebarStore = create<SidebarStore>()(
persist(
(set) => ({
collapsed: false,
hovering: false,
setCollapsed: (collapsed) => set({ collapsed }),
setHovering: (hovering) => set({ hovering }),
}),
{
name: 'sidebar-storage',
partialize: s => ({ collapsed: s.collapsed }), // only persist collapsed state
}
)
)With Immer (Complex Updates)
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
export const useTasksStore = create<TasksStore>()(
immer((set) => ({
tasks: [],
updateTask: (id, updates) =>
set((state) => {
const task = state.tasks.find((t) => t.id === id)
if (task) {
Object.assign(task, updates) // Direct mutation with Immer
}
}),
}))
)Selective Subscriptions [STR-C02, bp-04]
Only re-render when specific state changes:
See STR-C02 in registry.yaml for the wrong/right pattern.
Critical Rules [bp-01, bp-02, bp-03]
Don't Store Server Data
See bp-03 in registry.yaml for the wrong/right pattern.
Don't Duplicate URL State
See bp-01 and bp-02 in registry.yaml for the wrong/right patterns.
Reset Store When Needed
const initialState = {
selectedIds: [],
isModalOpen: false,
}
export const useProductsStore = create<ProductsStore>((set) => ({
...initialState,
// Actions...
reset: () => set(initialState),
}))
// Usage - reset on unmount
useEffect(() => {
return () => useProductsStore.getState().reset()
}, [])Checklist
- All
when: alwayspatterns present (STR-01 through STR-03 — verify against registry.yaml) - Each conditional pattern evaluated (STR-C01 through STR-C03 — check which apply)
- No
bad_practicesviolations (bp-01 through bp-04 — verify against registry.yaml)
Example
Creating a shopping cart store:
// packages/app/react/src/stores/useCartStore.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface CartItem {
id: string
productId: string
name: string
price: number
quantity: number
}
interface CartState {
items: CartItem[]
isOpen: boolean
}
interface CartActions {
addItem: (item: Omit<CartItem, 'id'>) => void
removeItem: (id: string) => void
updateQuantity: (id: string, quantity: number) => void
clearCart: () => void
setOpen: (open: boolean) => void
}
type CartStore = CartState & CartActions
export const useCartStore = create<CartStore>()(
persist(
(set) => ({
items: [],
isOpen: false,
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.productId === item.productId)
if (existing) {
return {
items: state.items.map((i) =>
i.productId === item.productId
? { ...i, quantity: i.quantity + item.quantity }
: i
),
}
}
return {
items: [...state.items, { ...item, id: crypto.randomUUID() }],
}
}),
removeItem: (id) =>
set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
updateQuantity: (id, quantity) =>
set((state) => ({
items: state.items.map((i) =>
i.id === id ? { ...i, quantity: Math.max(0, quantity) } : i
).filter((i) => i.quantity > 0),
})),
clearCart: () => set({ items: [] }),
setOpen: (open) => set({ isOpen: open }),
}),
{
name: 'cart-storage',
}
)
)
// Selectors
export const selectCartTotal = (state: CartStore) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
export const selectCartItemCount = (state: CartStore) =>
state.items.reduce((sum, item) => sum + item.quantity, 0)References
docs/FRONTEND.md— "State Management Strategy" section (decision framework for URL vs Zustand vs Dialog store vsuseState)
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- wheslleyrimar/BuildCLI-Agents★ 3same name
- theYahia/kaspi-mcp★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository45 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- agentSkill
- bounded-contextSkill
- clean-branchSkill
- commitSkill
- componentSkill
- controllerSkill
- db-modellingSkill
- ddd-modelingSkill
- ddd-specSkill
- design-systemSkill
- desktop-shellSkill
- e2eSkill
- entitySkill
- enumSkill
- errorsSkill
- eventSkill
- formSkill
- grill-with-docsSkill
- handlerSkill
- handoffSkill
- middlewareSkill
- migrateSkill
- prdSkill
- primitiveSkill
- projectionSkill
- projectorSkill
- querySkill
- repositorySkill
- reviewSkill
- routeSkill
- saas-researchSkill
- schedulerSkill
- schemaSkill
- sdkSkill
- serviceSkill
- spec-reviewSkill
- storybookSkill
- task-breakdownSkill
- testSkill
- trace-analysisSkill
- ui-compositionSkill
- usecaseSkill
- user-storiesSkill
- value-objectSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

