//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Estas receitas de skill são projetadas para o Claude Code, mas também funcionam com outros agentes de codificação de IA que suportam arquivos de skill/instrução.
O conteúdo completo do SKILL.md que você pode copiar para .claude/skills/zustand-state-management/SKILL.md:
---
name: zustand-state-management
description: "Construindo estado global escalável e performático com Zustand e TypeScript. Use quando solicitado: ajuda zustand, estado global, padrão de store, gerenciamento de estado, seletores zustand, middleware zustand, persist zustand, SSR zustand."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
# Zustand State Management
Você é um especialista em Zustand. Ajude os desenvolvedores a construir gerenciamento de estado escalável, performático e bem tipado.
## Regras de Arquitetura de Store
1. **Um store por domínio** - store de autenticação, store de carrinho, store de UI. Nunca um único store gigante.
2. **Estado plano** - Evite objetos profundamente aninhados. Normalize os dados como um banco de dados.
3. **Coloque ações junto com o estado** - Mantenha as ações no mesmo store que o estado que elas modificam.
4. **Use seletores** - Nunca se inscreva em todo o store. Sempre selecione o mínimo de dados necessário.
5. **Derive, não armazene** - Valores computados devem ser derivados em seletores, não armazenados.
## Padrões Principais
### Store Básico com TypeScript
```tsx
import \{ create \} from "zustand";
interface CounterState \{
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
\}
const useCounterStore = create<CounterState>((set) => (\{
count: 0,
increment: () => set((state) => (\{ count: state.count + 1 \})),
decrement: () => set((state) => (\{ count: state.count - 1 \})),
reset: () => set(\{ count: 0 \}),
\}));// RUIM - inscreve-se em todo o store, re-renderiza em QUALQUER alteração
function Component() \{
const store = useCounterStore();
return <span>\{store.count\}</span>;
\}
// BOM - inscreve-se apenas no count
function Component() \{
const count = useCounterStore((state) => state.count);
return <span>\{count\}</span>;
\}
// BOM - múltiplos valores com comparação superficial (shallow)
import \{ useShallow \} from "zustand/react/shallow";
function Component() \{
const \{ count, increment \} = useCounterStore(
useShallow((state) => (\{ count: state.count, increment: state.increment \}))
);
return <button onClick=\{increment\}>\{count\}</button>;
\}
// BOM - referência de ação estável (ações nunca mudam)
function Component() \{
const increment = useCounterStore((state) => state.increment);
// a referência de increment é estável, sem re-renderizações de outras alterações de estado
\}interface AuthSlice \{
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
\}
interface CartSlice \{
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
total: () => number;
\}
const createAuthSlice: StateCreator<AuthSlice & CartSlice, [], [], AuthSlice> = (
set
) => (\{
user: null,
login: async (credentials) => \{
const user = await api.login(credentials);
set(\{ user \});
\},
logout: () => set(\{ user: null \}),
\});
const createCartSlice: StateCreator<AuthSlice & CartSlice, [], [], CartSlice> = (
set,
get
) => (\{
items: [],
addItem: (item) => set((state) => (\{ items: [...state.items, item] \})),
removeItem: (id) =>
set((state) => (\{ items: state.items.filter((i) => i.id !== id) \})),
total: () => get().items.reduce((sum, item) => sum + item.price, 0),
\});
const useStore = create<AuthSlice & CartSlice>()((...args) => (\{
...createAuthSlice(...args),
...createCartSlice(...args),
\}));import \{ persist, createJSONStorage \} from "zustand/middleware";
const useSettingsStore = create<SettingsState>()(
persist(
(set) => (\{
theme: "light" as const,
language: "en",
setTheme: (theme: "light" | "dark") => set(\{ theme \}),
setLanguage: (language: string) => set(\{ language \}),
\}),
\{
name: "settings-storage",
storage: createJSONStorage(() => localStorage),
partialize: (state) => (\{
theme: state.theme,
language: state.language,
\}), // Apenas persista estes campos, não ações
\}
)
);import \{ devtools \} from "zustand/middleware";
const useStore = create<StoreState>()(
devtools(
(set) => (\{
count: 0,
increment: () =>
set(
(state) => (\{ count: state.count + 1 \}),
false,
"increment" // nome da ação no devtools
),
\}),
\{ name: "MyStore" \}
)
);import \{ immer \} from "zustand/middleware/immer";
const useTodoStore = create<TodoState>()(
immer((set) => (\{
todos: [],
toggleTodo: (id: string) =>
set((state) => \{
const todo = state.todos.find((t) => t.id === id);
if (todo) todo.completed = !todo.completed; // mutação direta é segura com immer
\}),
addTodo: (text: string) =>
set((state) => \{
state.todos.push(\{ id: crypto.randomUUID(), text, completed: false \});
\}),
\}))
);const useStore = create<StoreState>()(
devtools(
persist(
immer((set) => (\{
// definição do store
\})),
\{ name: "my-store" \}
),
\{ name: "MyStore" \}
)
);
// Ordem: immer (mais interno) -> persist -> devtools (mais externo)// stores/counter-store.ts
import \{ create \} from "zustand";
interface CounterState \{
count: number;
increment: () => void;
\}
export const useCounterStore = create<CounterState>((set) => (\{
count: 0,
increment: () => set((state) => (\{ count: state.count + 1 \})),
\}));
// Para prevenir mismatch de hidratação com middleware persist:
// components/HydrationGuard.tsx
"use client";
import \{ useEffect, useState \} from "react";
export function HydrationGuard(\{ children \}: \{ children: React.ReactNode \}) \{
const [hydrated, setHydrated] = useState(false);
useEffect(() => setHydrated(true), []);
return hydrated ? <>\{children\}</> : null; // ou um esqueleto
\}interface ProductState \{
products: Product[];
loading: boolean;
error: string | null;
fetchProducts: () => Promise<void>;
\}
const useProductStore = create<ProductState>((set) => (\{
products: [],
loading: false,
error: null,
fetchProducts: async () => \{
set(\{ loading: true, error: null \});
try \{
const products = await api.getProducts();
set(\{ products, loading: false \});
\} catch (error) \{
set(\{ error: (error as Error).message, loading: false \});
\}
\},
\}));import \{ renderHook, act \} from "@testing-library/react";
import \{ useCounterStore \} from "./counter-store";
// Reseta o store entre os testes
beforeEach(() => \{
useCounterStore.setState(\{ count: 0 \});
\});
test("increment aumenta a contagem", () => \{
const \{ result \} = renderHook(() =>
useCounterStore((state) => (\{
count: state.count,
increment: state.increment,
\}))
);
act(() => result.current.increment());
expect(result.current.count).toBe(1);
\});
// Testa o store diretamente sem React
test("store funciona sem componentes", () => \{
const \{ getState \} = useCounterStore;
expect(getState().count).toBe(0);
getState().increment();
expect(getState().count).toBe(1);
\});
## Exemplo de Trabalho
### Exemplo 1: Usuário pergunta "Configure um store Zustand para o meu carrinho de compras"
**Prompt do usuário:** "Preciso de um store de carrinho com adicionar, remover, atualizar quantidade e cálculo total."
**A resposta guiada pela skill produziria:**
```tsx
// stores/cart-store.ts
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
addItem: (item: Omit<CartItem, "quantity">) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
}
export const useCartStore = create<CartState>()(
persist(
immer((set) => ({
items: [],
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...item, quantity: 1 });
}
}),
removeItem: (id) =>
set((state) => {
state.items = state.items.filter((i) => i.id !== id);
}),
updateQuantity: (id, quantity) =>
set((state) => {
const item = state.items.find((i) => i.id === id);
if (item) item.quantity = Math.max(0, quantity);
}),
clearCart: () => set({ items: [] }),
})),
{
name: "cart-storage",
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ items: state.items }),
}
)
);
// Seletores derivados (definidos fora do store)
export const selectCartTotal = (state: CartState) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
export const selectCartCount = (state: CartState) =>
state.items.reduce((sum, item) => sum + item.quantity, 0);
Resposta guiada pela skill: Diagnosticar o padrão do seletor - provavelmente inscrevendo-se no store completo ou usando um seletor de objeto sem useShallow.
Esta skill fornece:
mkdir -p .claude/skills/zustand-state-management
# Cole o conteúdo da Receita em .claude/skills/zustand-state-management/SKILL.mdset({ count: 1 }) mescla com o estado existente, não o substitui. Use set(state => state, true) para uma substituição completa.onRehydrateStorage ou um guarda de hidratação.| Abordagem | Quando Usar |
|---|---|
| Jotai | Modelo de estado atômico, abordagem de baixo para cima |
| Valtio | Baseado em proxy, API mutável |
| Redux Toolkit | Grandes equipes, necessidades complexas de middleware, depuração com viagem no tempo |
| React Context | Estado simples compartilhado entre alguns componentes |
| Signals (Preact) | Reatividade granular sem seletores |
// Ruim: re-renderiza em QUALQUER alteração de estado
const store = useCounterStore();
// Bom: inscreve-se apenas no count
const count = useCounterStore((state) => state.count);import { useShallow } from "zustand/react/shallow";
const { count, increment } = useCounterStore(
useShallow((state) => ({ count: state.count, increment: state.increment }))
);useShallow quando seu seletor retornar um objeto (múltiplos valores)from "zustand/react/shallow"zustandimport { create } from "zustand";
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
}
const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));create<State>()const useStore = create<StoreState>()(
devtools(
persist(
immer((set) => ({ /* ... */ })),
{ name: "my-store" }
),
{ name: "MyStore" }
)
);persist salva o estado do store no localStorage (ou outro armazenamento)partialize permite selecionar quais campos persistir (excluir ações e dados derivados)createJSONStorage(() => localStorage) para o adaptador de armazenamentoset({ count: 1 }) mescla superficialmente com o estado existente (não substitui todo o store)set(newState, true) com o sinalizador de substituiçãoHydrationGuard que espera pelo useEffect antes de renderizaronRehydrateStorage do middleware persistStateCreatorcreate()// Reseta o store entre os testes
beforeEach(() => {
useCounterStore.setState({ count: 0 });
});
// Testa sem React
test("increment funciona", () => {
const { getState } = useCounterStore;
getState().increment();
expect(getState().count).toBe(1);
});setState para resetar entre os testesRevisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥