//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Envolva seu store com o middleware devtools para conectar à extensão do navegador Redux DevTools. Inspecione o estado, rastreie ações e use o depurador time-travel.
import { create } from "zustand";
import { devtools } from "zustand/middleware";
interface AppStore {
count: number;
increment: () => void;
decrement: () => void;
}
export const useAppStore = create<AppStore>()(
devtools(
(set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 }), false, "increment"),
decrement: () => set((s) => ({ count: s.count - 1 }), false, "decrement"),
}),
{ name: "AppStore" }
)
);// stores/order-store.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";
interface OrderItem {
id: string;
name: string;
quantity: number;
price: number;
}
interface OrderStore {
items: OrderItem[];
status: "idle" | "processing" | "completed" | "error";
discount: number;
addItem: (item: Omit<OrderItem, "quantity">) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
applyDiscount: (percent: number) => void;
submitOrder: () => Promise<void>;
resetOrder: () => void;
}
export const useOrderStore = create<OrderStore>()(
devtools(
(set, get) => ({
items: [],
status: "idle",
discount: 0,
addItem: (item) =>
set(
(s) => {
const existing = s.items.find((i) => i.id === item.id);
if (existing) {
return {
items: s.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...s.items, { ...item, quantity: 1 }] };
},
false,
{ type: "order/addItem", item } // Ação nomeada com payload
),
removeItem: (id) =>
set(
(s) => ({ items: s.items.filter((i) => i.id !== id) }),
false,
{ type: "order/removeItem", id }
),
updateQuantity: (id, quantity) =>
set(
(s) => ({
items: s.items.map((i) => (i.id === id ? { ...i, quantity } : i)),
}),
false,
{ type: "order/updateQuantity", id, quantity }
),
applyDiscount: (percent) =>
set(
{ discount: Math.min(100, Math.max(0, percent)) },
false,
{ type: "order/applyDiscount", percent }
),
submitOrder: async () => {
set({ status: "processing" }, false, "order/submitOrder/pending");
try {
const { items, discount } = get();
await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items, discount }),
});
set(
{ status: "completed", items: [], discount: 0 },
false,
"order/submitOrder/fulfilled"
);
} catch {
set({ status: "error" }, false, "order/submitOrder/rejected");
}
},
resetOrder: () =>
set(
{ items: [], status: "idle", discount: 0 },
false,
"order/reset"
),
}),
{
name: "OrderStore",
enabled: process.env.NODE_ENV === "development",
}
)
);// components/order-form.tsx
"use client";
import { useOrderStore } from "@/stores/order-store";
export function OrderForm() {
const items = useOrderStore((s) => s.items);
const status = useOrderStore((s) => s.status);
const discount = useOrderStore((s) => s.discount);
const addItem = useOrderStore((s) => s.addItem);
const removeItem = useOrderStore((s) => s.removeItem);
const updateQuantity = useOrderStore((s) => s.updateQuantity);
const applyDiscount = useOrderStore((s) => s.applyDiscount);
const submitOrder = useOrderStore((s) => s.submitOrder);
const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
const total = subtotal * (1 - discount / 100);
return (
<div>
<h2>Order ({status})</h2>
<button
onClick={() => addItem({ id: "p1", name: "Widget", price: 9.99 })}
>
Add Widget
</button>
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name} x{item.quantity} = ${(item.price * item.quantity).toFixed(2)}
<button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button>
<button onClick={() => removeItem(item.id)}>Remove</button>
</li>
))}
</ul>
<div>
<label>
Discount %:
<input
type="number"
value={discount}
onChange={(e) => applyDiscount(Number(e.target.value))}
/>
</label>
</div>
<p>Total: ${total.toFixed(2)}</p>
<button onClick={submitOrder} disabled={status === "processing"}>
{status === "processing" ? "Submitting..." : "Submit Order"}
</button>
</div>
);
}devtools intercepta cada chamada set e envia o diff do estado mais um nome de ação para a extensão Redux DevTools.set(state, replace, actionName) nomeia a ação no DevTools. Sem ele, as ações aparecem como "anonymous"."increment") ou objetos ({ type: "order/addItem", payload }) para informações de depuração mais ricas.name na configuração do devtools define o nome do store exibido no seletor de instância do DevTools.setState.enabled controla se o DevTools se conecta. Defina como false em produção para evitar sobrecarga.Múltiplos stores no DevTools:
// Cada store recebe sua própria instância do DevTools
const useAuthStore = create<AuthState>()(
devtools((set) => ({ ... }), { name: "AuthStore" })
);
const useCartStore = create<CartState>()(
devtools((set) => ({ ... }), { name: "CartStore" })
);
// Ambos aparecem como instâncias separadas no Redux DevToolsConectando a um servidor DevTools remoto:
devtools(storeCreator, {
name: "RemoteStore",
// Conecta-se à instância remota do Redux DevTools
// Útil para depurar aplicativos móveis ou embarcados
});Opção trace para rastros de pilha:
devtools(storeCreator, {
name: "Store",
trace: true, // Mostra o rastreamento de pilha para cada ação
traceLimit: 25, // Número máximo de quadros de pilha a serem mostrados
});Com outros middlewares:
// devtools deve sempre ser o middleware mais externo
const useStore = create<State>()(
devtools(
persist(
immer((set) => ({ ... })),
{ name: "persist-key" }
),
{ name: "DevToolsName" }
)
);devtools não altera o tipo do store. Todos os tipos são preservados.actionName em set é tipado como string | { type: string; [key: string]: unknown }.// set totalmente tipado com nome da ação
set(
(state) => ({ count: state.count + 1 }), // Atualizador de estado
false, // flag replace
{ type: "counter/increment", by: 1 } // Ação para o DevTools
);devtools não for o middleware mais externo, o DevTools pode não capturar todas as alterações de estado corretamente. Sempre envolva todo o resto dentro de devtools.set (nome da ação) só funciona quando o middleware devtools está ativo. Sem ele, o terceiro argumento é silenciosamente ignorado.enabled: false em produção.serialize para filtrar ou limitar o que é enviado.| Abordagem | Prós | Contras |
|---|---|---|
| Middleware Redux DevTools | Time-travel, inspeção visual de estado | Requer extensão do navegador, sobrecarga em produção |
| Middleware de logging no console | Sem dependências, simples | Sem UI visual, sem time-travel |
| React DevTools | Integrado, mostra o estado do componente | Não pode inspecionar stores Zustand diretamente |
| Painel de depuração personalizado | Adaptado ao seu aplicativo | Precisa ser construído e mantido |
set(
(s) => ({ count: s.count + 1 }),
false,
"increment" // Nome da ação exibido no DevTools
);set.{ type: "order/addItem", payload }.setState.devtools(storeCreator, {
name: "AppStore",
enabled: process.env.NODE_ENV === "development",
});devtools não for o middleware mais externo, ele pode não capturar todas as alterações de estado corretamente.devtools(persist(immer((set) => ({ ... })))).devtools está ativo.serialize para filtrar o que é enviado.enabled: false.devtools(storeCreator, {
name: "Store",
trace: true,
traceLimit: 25,
});trace adiciona um rastreamento de pilha a cada ação no DevTools.string | { type: string; [key: string]: unknown }.devtools não altera a assinatura de tipo do store.set(
(state) => ({ count: state.count + 1 }),
false,
{ type: "counter/increment", by: 1 }
);Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥