From harness-claude
Enables mutable-style state updates in Zustand stores using Immer middleware for cleaner nested mutations, array updates by index/ID, and avoiding verbose spreads.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Write mutable-style state updates in Zustand stores with the Immer middleware for cleaner nested mutations
Implements Zustand middleware for persistence (persist), devtools integration, Immer immutability, and custom store enhancements. Guides composition, best practices, partialize, and migrations.
Provides Zustand 5.x patterns for React state management: slices, middleware, Immer, useShallow, persistence, selectors, devtools, async actions, and anti-patterns with TanStack Query integration. Use for global client state without boilerplate.
Guides Zustand state management in React and vanilla JS: stores, selectors, persistence, devtools, performance patterns, and non-React access.
Share bugs, ideas, or general feedback.
Write mutable-style state updates in Zustand stores with the Immer middleware for cleaner nested mutations
immer middleware from zustand/middleware/immer.set, mutate the state object directly — Immer produces a new immutable state behind the scenes.set callback when using Immer — just mutate.immer middleware should be the innermost middleware when combined with others.immer as a peer dependency: 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);
}
}),
}))
);
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:
const { items } = state; items.push(x) — this works but is confusing; prefer state.items.push(x))Map, Set, or Date objects unless you enable MapSet pluginWhen 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.
https://zustand.docs.pmnd.rs/middlewares/immer