install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/state-zustand-immer" ~/.claude/skills/intense-visions-harness-engineering-state-zustand-immer-a89977 && rm -rf "$T"
manifest:
agents/skills/codex/state-zustand-immer/SKILL.mdsource content
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
- Wrap the store creator with the
middleware fromimmer
.zustand/middleware/immer - Inside
, mutate theset
object directly — Immer produces a new immutable state behind the scenes.state - Do NOT return a value from the
callback when using Immer — just mutate.set - The
middleware should be the innermost middleware when combined with others.immer - Install
as a peer dependency:immer
.npm install immer
// 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:
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:
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 (
— this works but is confusing; preferconst { items } = state; items.push(x)
)state.items.push(x) - Immer cannot track mutations to
,Map
, orSet
objects unless you enable MapSet pluginDate - 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
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- 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.