//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Envuelve tu store con el middleware devtools para conectarte a la extensión de navegador Redux DevTools. Inspecciona el state, rastrea acciones y usa depuración con viaje en el tiempo.
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 } // Acción con nombre y 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>Pedido ({status})</h2>
<button
onClick={() => addItem({ id: "p1", name: "Widget", price: 9.99 })}
>
Añadir 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)}>Eliminar</button>
</li>
))}
</ul>
<div>
<label>
Descuento %:
<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" ? "Enviando..." : "Enviar pedido"}
</button>
</div>
);
}devtools intercepta cada llamada a set y envía el diff del state más un nombre de acción a la extensión Redux DevTools.set(state, replace, actionName) nombra la acción en DevTools. Sin él, las acciones aparecen como "anonymous"."increment") u objetos ({ type: "order/addItem", payload }) para una depuración más rica.name en la configuración de devtools establece el nombre del store que se muestra en el selector de instancias de DevTools.setState.enabled controla si DevTools se conecta. Establécela en false en producción para evitar sobrecarga.Varios stores en DevTools:
// Cada store obtiene su propia instancia de DevTools
const useAuthStore = create<AuthState>()(
devtools((set) => ({ ... }), { name: "AuthStore" })
);
const useCartStore = create<CartState>()(
devtools((set) => ({ ... }), { name: "CartStore" })
);
// Ambos aparecen como instancias separadas en Redux DevToolsConexión a un servidor remoto de DevTools:
devtools(storeCreator, {
name: "RemoteStore",
// Conectar a una instancia remota de Redux DevTools
// Útil para depurar apps móviles o embebidas
});Opción trace para stack traces:
devtools(storeCreator, {
name: "Store",
trace: true, // Mostrar stack trace de cada acción
traceLimit: 25, // Máximo de frames del stack a mostrar
});Con otros middleware:
// devtools debe ser siempre el middleware más externo
const useStore = create<State>()(
devtools(
persist(
immer((set) => ({ ... })),
{ name: "persist-key" }
),
{ name: "DevToolsName" }
)
);devtools no cambia el tipo del store. Todos los tipos se conservan.set está tipado como string | { type: string; [key: string]: unknown }.// set completamente tipado con nombre de acción
set(
(state) => ({ count: state.count + 1 }), // Actualizador de state
false, // bandera replace
{ type: "counter/increment", by: 1 } // Acción para DevTools
);devtools no es el middleware más externo, DevTools puede no capturar correctamente todos los cambios de state. Envuelve siempre todo lo demás dentro de devtools.set (nombre de acción) solo funciona cuando el middleware devtools está activo. Sin él, el tercer argumento se ignora silenciosamente.enabled: false en producción.serialize para filtrar o limitar lo que se envía.| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Middleware de Redux DevTools | Viaje en el tiempo, inspección visual del state | Requiere extensión de navegador, sobrecarga en producción |
| Middleware de logging en consola | Sin dependencias, simple | Sin interfaz visual, sin viaje en el tiempo |
| React DevTools | Integrado, muestra el state del componente | No puede inspeccionar stores de Zustand directamente |
| Panel de depuración personalizado | Adaptado a tu app | Debes construirlo y mantenerlo |
set(
(s) => ({ count: s.count + 1 }),
false,
"increment" // Nombre de acción mostrado en DevTools
);set.{ type: "order/addItem", payload }.setState.devtools(storeCreator, {
name: "AppStore",
enabled: process.env.NODE_ENV === "development",
});devtools no es el middleware más externo, puede no capturar correctamente todos los cambios de state.devtools(persist(immer((set) => ({ ... })))).devtools está activo.serialize para filtrar lo que se envía.enabled: false.devtools(storeCreator, {
name: "Store",
trace: true,
traceLimit: 25,
});trace añade un stack trace a cada acción en DevTools.string | { type: string; [key: string]: unknown }.devtools no cambia la firma de tipos del store.set(
(state) => ({ count: state.count + 1 }),
false,
{ type: "counter/increment", by: 1 }
);Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥