En esta página
Estado Global con Zustand
¿Por qué Zustand?
Zustand es una librería de gestión de estado minimalista para React con una API sorprendentemente simple. En comparación con Redux, no requiere reducers, actions, action creators ni dispatch. Solo defines un store con estado y acciones, y listo.
npm install zustandTu primer store
import { create } from 'zustand';
interface ContadorStore {
conteo: number;
incrementar: () => void;
decrementar: () => void;
reiniciar: () => void;
incrementarPor: (cantidad: number) => void;
}
const useContador = create<ContadorStore>((set) => ({
conteo: 0,
incrementar: () => set((state) => ({ conteo: state.conteo + 1 })),
decrementar: () => set((state) => ({ conteo: Math.max(0, state.conteo - 1) })),
reiniciar: () => set({ conteo: 0 }),
incrementarPor: (cantidad) => set((state) => ({ conteo: state.conteo + cantidad })),
}));
function Contador(): React.JSX.Element {
// Selección granular: solo re-renderiza cuando conteo cambia
const conteo = useContador((s) => s.conteo);
const incrementar = useContador((s) => s.incrementar);
const decrementar = useContador((s) => s.decrementar);
return (
<div>
<button type="button" onClick={decrementar}>−</button>
<span>{conteo}</span>
<button type="button" onClick={incrementar}>+</button>
</div>
);
}Selectores — el patrón de rendimiento clave
El selector es una función que extrae solo la parte del store que el componente necesita:
// ❌ Se re-renderiza cuando CUALQUIER cosa en el store cambia
const storeCompleto = useAppStore();
// ✅ Solo se re-renderiza cuando cambia el nombre
const nombre = useAppStore((s) => s.usuario?.nombre);
// ✅ Múltiples valores: usa shallow para comparación correcta
import { shallow } from 'zustand/shallow';
const { nombre, email } = useAppStore(
(s) => ({ nombre: s.usuario?.nombre, email: s.usuario?.email }),
shallow
);Estado asíncrono en Zustand
interface ProductosStore {
productos: Producto[];
cargando: boolean;
error: string | null;
cargarProductos: () => Promise<void>;
agregarProducto: (p: Omit<Producto, 'id'>) => Promise<void>;
}
const useProductos = create<ProductosStore>((set, get) => ({
productos: [],
cargando: false,
error: null,
cargarProductos: async () => {
set({ cargando: true, error: null });
try {
const res = await fetch('/api/productos');
if (!res.ok) throw new Error(`Error ${res.status}`);
const productos = await res.json() as Producto[];
set({ productos, cargando: false });
} catch (err) {
set({
cargando: false,
error: err instanceof Error ? err.message : 'Error desconocido',
});
}
},
agregarProducto: async (producto) => {
const res = await fetch('/api/productos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(producto),
});
const nuevo = await res.json() as Producto;
// Actualización optimista: agregar al store sin esperar revalidación
set((state) => ({ productos: [...state.productos, nuevo] }));
},
}));Middleware devtools
El middleware devtools conecta Zustand con Redux DevTools Extension:
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create<MiStore>()(
devtools(
(set) => ({
// tu store aquí
}),
{ name: 'MiAplicacion', enabled: process.env.NODE_ENV !== 'production' }
)
);Con devtools puedes ver el historial de acciones, inspeccionar el estado en cada momento y hacer time-travel debugging.
Comparación con Redux Toolkit
// Redux Toolkit — más verboso pero más convenciones
const contadorSlice = createSlice({
name: 'contador',
initialState: { valor: 0 },
reducers: {
incrementar: (state) => { state.valor += 1; },
decrementar: (state) => { state.valor -= 1; },
},
});
// Zustand — más simple, misma funcionalidad
const useContador = create((set) => ({
valor: 0,
incrementar: () => set((s) => ({ valor: s.valor + 1 })),
decrementar: () => set((s) => ({ valor: s.valor - 1 })),
}));| Aspecto | Zustand | Redux Toolkit |
|---|---|---|
| Setup | Mínimo | Moderado |
| Boilerplate | Muy poco | Poco (mejorado en RTK) |
| TypeScript | Excelente | Excelente |
| DevTools | Sí (middleware) | Sí (nativo) |
| Middleware | Flexible | Thunk, Saga, RTK Query |
| Bundle size | ~1KB | ~12KB |
| Curva aprendizaje | Baja | Media |
Zustand es la elección ideal para proyectos pequeños y medianos. Para equipos grandes o proyectos enterprise con convenciones estrictas, Redux Toolkit puede ser la mejor opción.
Usa selectores para evitar re-renders innecesarios
En lugar de useStore() que suscribe al store completo, usa useStore(s => s.campo) para suscribirte solo a lo que necesitas. Si el componente solo usa el nombre del usuario, solo se re-renderizará cuando el nombre cambie, no cuando cambie cualquier otra parte del store.
Zustand vs Redux Toolkit — cuándo usar cada uno
Zustand es más simple (menos boilerplate, sin actions/reducers/dispatch), ideal para la mayoría de proyectos. Redux Toolkit brilla en aplicaciones muy grandes con equipos grandes que necesitan convenciones estrictas, time-travel debugging avanzado y ecosistema maduro de middleware.
El middleware persist serializa el estado en localStorage
persist de Zustand guarda el estado en localStorage (o sessionStorage). Solo persiste datos serializables: no funciones, clases, Dates o Maps directamente. Usa partialize para especificar qué parte del estado persistir y evita guardar datos sensibles del usuario en localStorage.
Inicia sesión para guardar tu progreso