tianyili/skills · Archived

zustand

Use when the user mentions Zustand, needs a store pattern, global state, persist state, or is migrating from Redux/Context to Zustand in React or vanilla JS.

First seen Mar 21, 2026

Installation

$ npx skills add tianyili/skills --skill zustand

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from tianyili/skills.

npx skills add tianyili/skills

Browse all from tianyili/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 1
Default branch main
Open issues 1
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents cursor

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,054 B
  • docs SUMMARY.md 172 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 7 installs

SKILL.md

Zustand

Guidance for using Zustand (create, stores, middleware, and scaling patterns). No provider required.

Cursor rule

A file-scoped rule is provided at [rules/zustand-stores.mdc](rules/zustand-stores.mdc). Copy it to your project’s .cursor/rules/ to get automatic reminders when editing store files.

When to Apply

  • Adding or refactoring Zustand stores
  • Persisting state (e.g. localStorage)
  • Splitting a large store into slices
  • User asks about Zustand, global state, or store structure

Store Basics

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  inc: () => set((s) => ({ count: s.count + 1 })),
  reset: () => set({ count: 0 }),
}))
  • set(state | fn): Replace state or use updater set((s) => ({ ... })).
  • get(): Read current state inside actions (e.g. get().count).
  • Prefer updater form for derived updates: set((s) => ({ count: s.count + 1 })).

Selectors and Re-renders

Select only what the component needs to avoid unnecessary re-renders.

// Bad – re-renders on any store change
const state = useStore()

// Good – re-renders only when count changes
const count = useStore((s) => s.count)
const inc = useStore((s) => s.inc)

Use shallow when selecting multiple fields:

import { useShallow } from 'zustand/react/shallow'

const { count, inc } = useStore(useShallow((s) => ({ count: s.count, inc: s.inc })))

In v5 this is a correctness requirement, not just a perf nicety: selectors must return stable references, and a selector that returns a fresh object/array each call without useShallow throws "Maximum update depth exceeded".

Middleware

Persist (e.g. localStorage)

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      token: null,
      setToken: (t) => set({ token: t }),
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() => localStorage),
      partialize: (s) => ({ token: s.token }), // optional: persist only part
    },
  ),
)

For async storage (e.g. React Native), use a storage adapter that returns Promises; the store will rehydrate when the promise resolves.

Devtools

import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set) => ({ ... }),
    { name: 'MyStore' },
  ),
)

Combining Middleware

Order: store creator innermost, then devtools, then persist (so persist wraps the devtools-wrapped store).

create(
  persist(
    devtools((set) => ({ ... }), { name: 'AppStore' }),
    { name: 'app-storage' },
  ),
)

Slices Pattern (Scaling)

Split one store into slice creators; compose them in a single create call.

const createAuthSlice = (set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
})

const createUiSlice = (set) => ({
  theme: 'light',
  setTheme: (theme) => set({ theme }),
})

const useStore = create((...a) => ({
  ...createAuthSlice(...a),
  ...createUiSlice(...a),
}))

Each slice receives (set, get, store) and returns a plain object; avoid naming collisions between slices.

Immer (Mutable-style Updates)

Use immer middleware when updaters are complex or nested.

import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    nested: { a: 1, b: { c: 2 } },
    updateC: (v) => set((s) => {
      s.nested.b.c = v
    }),
  })),
)

TypeScript

Type the store state and actions; infer hook types from create.

interface BearState {
  bears: number
  addBear: () => void
}

const useBearStore = create<BearState>()((set) => ({
  bears: 0,
  addBear: () => set((s) => ({ bears: s.bears + 1 })),
}))

Use create<State>()(...) so middlewares keep correct typing.

Vanilla (Outside React)

Same store can be used without React: useStore.getState(), useStore.setState(...), useStore.subscribe((state) => { ... }).

Anti-patterns

  • Don’t put everything in one giant store; prefer slices or multiple stores by domain.
  • Don’t select the whole store in components; use selectors or useShallow for multiple fields.
  • Don’t forget partialize with persist when the store has non-serializable values (e.g. functions, class instances).

Summary

Need Use
Basic store create((set) => ({ ... }))
Persist persist(..., { name, storage })
DevTools devtools(...)
Nested updates immer
Scale store Slice functions + spread in create
Fewer re-renders Selectors or useShallow