Skip to content
Back to skills

State Zustand Immer

ASecurity

> Write mutable-style state updates in Zustand stores with the Immer middleware for cleaner nested mutations

  • 20 stars
  • 0 votes
  • 0 copies
  • 8 views
  • Added September 11, 2026
toolstypescriptgo

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned September 11, 2026

npx -y skills add Intense-Visions/harness-engineering --skill state-zustand-immer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of State Zustand Immer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for State Zustand Immer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/intense-visions-state-zustand-immer/badge)](https://www.skillsdirectory.com/skills/intense-visions-state-zustand-immer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Zustand Immer

> Write mutable-style state updates in Zustand stores with the Immer middleware for cleaner nested mutations

## When to Use

- Store has deeply nested state that requires verbose spread operators to update
- Updating items in arrays by index or by ID lookup
- Teams familiar with Redux Toolkit's Immer integration wanting the same ergonomics
- Any update where the immutable spread version is hard to read or error-prone

## Instructions

1. Wrap the store creator with the `immer` middleware from `zustand/middleware/immer`.
2. Inside `set`, mutate the `state` object directly — Immer produces a new immutable state behind the scenes.
3. Do NOT return a value from the `set` callback when using Immer — just mutate.
4. The `immer` middleware should be the innermost middleware when combined with others.
5. Install `immer` as a peer dependency: `npm install immer`.

```typescript
// stores/kanban-store.ts
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

interface Card {
  id: string;
  title: string;
  description: string;
}

interface Column {
  id: string;
  title: string;
  cards: Card[];
}

interface KanbanStore {
  columns: Column[];
  addCard: (columnId: string, card: Card) => void;
  moveCard: (fromCol: string, toCol: string, cardId: string) => void;
  updateCard: (columnId: string, cardId: string, updates: Partial<Card>) => void;
  removeCard: (columnId: string, cardId: string) => void;
}

export const useKanbanStore = create<KanbanStore>()(
  immer((set) => ({
    columns: [],

    addCard: (columnId, card) =>
      set((state) => {
        const column = state.columns.find((c) => c.id === columnId);
        if (column) column.cards.push(card);
      }),

    moveCard: (fromCol, toCol, cardId) =>
      set((state) => {
        const source = state.columns.find((c) => c.id === fromCol);
        const target = state.columns.find((c) => c.id === toCol);
        if (!source || !target) return;

        const cardIndex = source.cards.findIndex((c) => c.id === cardId);
        if (cardIndex === -1) return;

        const [card] = source.cards.splice(cardIndex, 1);
        target.cards.push(card);
      }),

    updateCard: (columnId, cardId, updates) =>
      set((state) => {
        const column = state.columns.find((c) => c.id === columnId);
        const card = column?.cards.find((c) => c.id === cardId);
        if (card) Object.assign(card, updates);
      }),

    removeCard: (columnId, cardId) =>
      set((state) => {
        const column = state.columns.find((c) => c.id === columnId);
        if (column) {
          column.cards = column.cards.filter((c) => c.id !== cardId);
        }
      }),
  }))
);
```

## Details

**Without Immer (comparison):** The `moveCard` action without Immer:

```typescript
moveCard: (fromCol, toCol, cardId) =>
  set((state) => ({
    columns: state.columns.map((col) => {
      if (col.id === fromCol) {
        return { ...col, cards: col.cards.filter((c) => c.id !== cardId) };
      }
      if (col.id === toCol) {
        const card = state.columns.find((c) => c.id === fromCol)!.cards.find((c) => c.id === cardId)!;
        return { ...col, cards: [...col.cards, card] };
      }
      return col;
    }),
  })),
```

**Middleware stacking with Immer:**

```typescript
create<Store>()(
  devtools(
    persist(
      immer((set) => ({
        /* ... */
      })),
      { name: 'key' }
    ),
    { name: 'Store' }
  )
);
// Order: devtools(persist(immer(...)))
```

**Immer rules:**

- Either mutate state OR return a new value — never both
- Do not destructure state at the top level (`const { items } = state; items.push(x)` — this works but is confusing; prefer `state.items.push(x)`)
- Immer cannot track mutations to `Map`, `Set`, or `Date` objects unless you enable MapSet plugin
- Immer adds ~5KB to the bundle — only use it when the readability benefit justifies it

**When NOT to use Immer:** For flat state or simple updates (`set({ count: state.count + 1 })`), Immer adds overhead without readability benefit. Use it specifically for nested mutations.

## Source

https://zustand.docs.pmnd.rs/middlewares/immer

## Process

1. Read the instructions and examples in this document.
2. Apply the patterns to your implementation, adapting to your specific context.
3. Verify your implementation against the details and edge cases listed above.

## Harness Integration

- **Type:** knowledge — this skill is a reference document, not a procedural workflow.
- **No tools or state** — consumed as context by other skills and agents.

## Success Criteria

- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.

Files in this skill

  • SKILL.md4.7 KB
  • skill.yaml688 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.