Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

zustand-5 — Claude Code skill: lightweight client state

Zustand 5 state management patterns for Claude Code: basic stores, selectors, persist middleware, and slice composition. Prevents unnecessary re-renders and keeps state logic clean.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add prowler-cloud/prowler --skill zustand-5 -a claude-code
Open on GitHub

What zustand-5 does

Zustand is a lightweight state library without Redux boilerplate. This skill covers how to create a store with actions, subscribe to specific fields via selectors, persist data to localStorage, handle async operations, use Immer for convenient mutations, and compose stores from slices.

Selectors are key: a component watches only the fields it needs, so it won't re-render when other state changes. For multiple fields, use useShallow from zustand/react/shallow. Outside React, access state with getState() and listen for changes with subscribe().

The skill installs as a project module and works across all major AI editors. Examples cover real scenarios: counters, persisted settings, user loading with error handling, shopping cart.

What it helps with

  • Build client state without Redux verbosity
  • Optimize component re-renders when state updates
  • Persist data to localStorage and restore on load
  • Handle async actions inside a store
  • Organize large state into logical slices

Example prompts for the agent

  • Create a Zustand store for a counter with increment and decrement actions
  • Add persist middleware to save theme preference to localStorage
  • Write a selector that fetches a user by ID and updates the store

How to install zustand-5

1Skills CLI (Vercel)

npx skills add prowler-cloud/prowler --skill zustand-5 -a claude-code

2Manually: put the files in place

~/.claude/skills/zustand-5/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/zustand-5/

From the same set4

The whole set →

Similar

visual-edit-precision Adit Jain Skill for targeted UI changes when you show elements on a screenshot or point them out in code. Edits only what you selected, without refactoring the entire component. Claude Code CX Codex Cursor 2,800installs image vercel-labs Turns JSON specs into SVG and PNG images via Satori. Useful for generating OG images, social cards, and rendering AI-generated image specs. Claude Code CX Codex Cursor +1 1,500installs huggingface-gradio huggingface This skill helps you build and modify Python Gradio interfaces for machine learning demos. It provides patterns for layouts, UI components, and chatbot workflows. Claude Code CX Codex Cursor +1 2,300installs text-and-bitmaptext phaserjs Helps AI agents configure and render text in Phaser 4. Useful when styling Canvas text, handling word wrapping, and setting up bitmap fonts. Claude Code CX Codex Cursor +1 816installs write-e2e-tests tldraw Guides Playwright E2E testing for tldraw, including UI actions, menus, and canvas shapes. Use it when adding coverage for SDK examples or the tldraw.com client. Claude Code CX Codex Cursor +1 430installs game-object-components phaserjs Reference guide for Phaser 4 game object components and mixins. Helps coding agents handle Transform, Depth, and WebGL effects accurately. Claude Code CX Codex Cursor +1 796installs frontend-design official anthropics This skill guides intentional visual design for new interfaces and redesigns. Use it when a generic generated UI is not enough. Claude Code Claude.ai 1,025,790installs design-taste-frontend Leonxlnx This skill directs the design of public pages and work screens, then checks the result in a browser through screenshots. Use it when an interface feels generic or unfinished. Claude Code CX Codex Cursor +1 673,100installs

Questions about zustand-5

How do I install zustand-5 for Claude Code?

Run npx skills add prowler-cloud/prowler --skill zustand-5 -a claude-code, then open ~/.claude/skills/zustand-5/SKILL.md.

What does zustand-5 provide?

Ready-to-use patterns and code examples for Zustand 5: basic stores, selectors for optimization, localStorage persistence, async actions, slice composition, and Immer integration.

Why do selectors matter in Zustand?

A selector watches only the fields a component needs, so it skips re-renders when other state changes. Use useShallow for multiple fields to avoid unnecessary updates.

Source text

SKILL.md · Apache-2.0
---
name: zustand-5
description: >
  Zustand 5 state management patterns.
  Trigger: When implementing client-side state with Zustand (stores, selectors, persist middleware, slices).
license: Apache-2.0
metadata:
  author: prowler-cloud
  version: "1.0"
  scope: [root, ui]
  auto_invoke: "Using Zustand stores"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
---

## Basic Store

```typescript
import { create } from "zustand";

interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Usage
function Counter() {
  const { count, increment, decrement } = useCounterStore();
  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}
```

## Persist Middleware

```typescript
import { create } from "zustand";
import { persist } from "zustand/middleware";

interface SettingsStore {
  theme: "light" | "dark";
  language: string;
  setTheme: (theme: "light" | "dark") => void;
  setLanguage: (language: string) => void;
}

const useSettingsStore = create<SettingsStore>()(
  persist(
    (set) => ({
      theme: "light",
      language: "en",
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
    }),
    {
      name: "settings-storage",  // localStorage key
    }
  )
);
```

## Selectors (Zustand 5)

```typescript
// ✅ Select specific fields to prevent unnecessary re-renders
function UserName() {
  const name = useUserStore((state) => state.name);
  return <span>{name}</span>;
}

// ✅ For multiple fields, use useShallow
import { useShallow } from "zustand/react/shallow";

function UserInfo() {
  const { name, email } = useUserStore(
    useShallow((state) => ({ name: state.name, email: state.email }))
  );
  return <div>{name} - {email}</div>;
}

// ❌ AVOID: Selecting entire store (causes re-render on any change)
const store = useUserStore();  // Re-renders on ANY state change
```

## Async Actions

```typescript
interface UserStore {
  user: User | null;
  loading: boolean;
  error: string | null;
  fetchUser: (id: string) => Promise<void>;
}

const useUserStore = create<UserStore>((set) => ({
  user: null,
  loading: false,
  error: null,

  fetchUser: async (id) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch(`/api/users/${id}`);
      const user = await response.json();
      set({ user, loading: false });
    } catch (error) {
      set({ error: "Failed to fetch user", loading: false });
    }
  },
}));
```

## Slices Pattern

```typescript
// userSlice.ts
interface UserSlice {
  user: User | null;
  setUser: (user: User) => void;
  clearUser: () => void;
}

const createUserSlice = (set): UserSlice => ({
  user: null,
  setUser: (user) => set({ user }),
  clearUser: () => set({ user: null }),
});

// cartSlice.ts
interface CartSlice {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
}

const createCartSlice = (set): CartSlice => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(i => i.id !== id)
  })),
});

// store.ts
type Store = UserSlice & CartSlice;

const useStore = create<Store>()((...args) => ({
  ...createUserSlice(...args),
  ...createCartSlice(...args),
}));
```

## Immer Middleware

```typescript
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";

interface TodoStore {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: string) => void;
}

const useTodoStore = create<TodoStore>()(
  immer((set) => ({
    todos: [],

    addTodo: (text) => set((state) => {
      // Mutate directly with Immer!
      state.todos.push({ id: crypto.randomUUID(), text, done: false });
    }),

    toggleTodo: (id) => set((state) => {
      const todo = state.todos.find(t => t.id === id);
      if (todo) todo.done = !todo.done;
    }),
  }))
);
```

## DevTools

```typescript
import { create } from "zustand";
import { devtools } from "zustand/middleware";

const useStore = create<Store>()(
  devtools(
    (set) => ({
      // store definition
    }),
    { name: "MyStore" }  // Name in Redux DevTools
  )
);
```

## Outside React

```typescript
// Access store outside components
const { count, increment } = useCounterStore.getState();
increment();

// Subscribe to changes
const unsubscribe = useCounterStore.subscribe(
  (state) => console.log("Count changed:", state.count)
);
```

© prowler-cloud · Apache-2.0 · prowler-cloud/prowler

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor