Zustand: El State Management Minimalista que Está Reemplazando a Redux en 2025
Hola HaWkers, ¿estás cansado de escribir toneladas de boilerplate para gestionar estado en React con Redux? ¿O confundido con la complejidad de Context API para estados globales?
En 2025, Zustand emergió como la solución minimalista que los desarrolladores React estaban esperando. Con solo ~1KB de tamaño y una API increíblemente simple, Zustand está rápidamente reemplazando a Redux en nuevos proyectos y migraciones.
La biblioteca creció un 300% en adopción el último año, y grandes empresas ya migraron de Redux a Zustand, reduciendo código hasta un 70% y mejorando el rendimiento.
El Problema con Redux y Alternativas
Redux: Poderoso pero Verboso
// Redux - mucho boilerplate para algo simple
// 1. Actions
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 2. Action Creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });
// 3. Reducer
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
// 4. Store
const store = createStore(counterReducer);
// 5. Provider
<Provider store={store}>
<App />
</Provider>
// 6. Uso en el componente
function Counter() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
{count}
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
// ¡~50 líneas para un contador! 😱Context API: Simple pero con Problemas
// Context API - más simple, pero re-renders innecesarios
const CountContext = createContext();
function CountProvider({ children }) {
const [count, setCount] = useState(0);
// ❌ Problema: TODO componente que usa el contexto se re-renderiza
// incluso si usa solo parte del estado
return (
<CountContext.Provider value={{ count, setCount }}>
{children}
</CountContext.Provider>
);
}
function Counter() {
const { count, setCount } = useContext(CountContext);
// Se re-renderiza incluso si otro valor en el contexto cambia
return <div>{count}</div>;
}
Zustand: Minimalismo y Rendimiento
El mismo contador en Zustand:
import { create } from 'zustand';
// Define store - ¡SOLO ESTO!
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));
// Usa en cualquier componente - ¡sin Provider!
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return (
<div>
{count}
<button onClick={increment}>+</button>
</div>
);
}
// ¡~15 líneas, rendimiento optimizado automáticamente! ✨Ventajas:
- ✅ Zero boilerplate: Sin actions, reducers, providers
- ✅ Tiny bundle: 1KB (Redux: ~10KB)
- ✅ Sin Provider: Funciona fuera de React también
- ✅ Rendimiento: Re-renders optimizados por defecto
- ✅ TypeScript: Soporte nativo excelente
- ✅ DevTools: Integración con Redux DevTools
Casos de Uso Avanzados
1. Store de Autenticación Completa
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface User {
id: string;
name: string;
email: string;
}
interface AuthStore {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
updateUser: (user: Partial<User>) => void;
}
export const useAuthStore = create<AuthStore>()(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (email, password) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const { user, token } = await response.json();
set({
user,
token,
isAuthenticated: true
});
} catch (error) {
console.error('Login failed:', error);
throw error;
}
},
logout: () => {
set({
user: null,
token: null,
isAuthenticated: false
});
},
updateUser: (updates) => {
set((state) => ({
user: state.user ? { ...state.user, ...updates } : null
}));
}
}),
{
name: 'auth-storage', // Persiste en localStorage
partialize: (state) => ({ // Persiste solo campos necesarios
user: state.user,
token: state.token
})
}
)
);
// Uso
function Profile() {
const user = useAuthStore((state) => state.user);
const logout = useAuthStore((state) => state.logout);
if (!user) return <Login />;
return (
<div>
<h1>Hola, {user.name}</h1>
<button onClick={logout}>Salir</button>
</div>
);
}2. Shopping Cart con Cálculos Derivados
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
// Valores computados
totalItems: () => number;
totalPrice: () => number;
// Acciones
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
}
export const useCartStore = create<CartStore>()(
devtools((set, get) => ({
items: [],
// Valores computados como funciones
totalItems: () => {
return get().items.reduce((sum, item) => sum + item.quantity, 0);
},
totalPrice: () => {
return get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
},
addItem: (newItem) => {
set((state) => {
const existingItem = state.items.find((item) => item.id === newItem.id);
if (existingItem) {
return {
items: state.items.map((item) =>
item.id === newItem.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
items: [...state.items, { ...newItem, quantity: 1 }]
};
});
},
removeItem: (id) => {
set((state) => ({
items: state.items.filter((item) => item.id !== id)
}));
},
updateQuantity: (id, quantity) => {
if (quantity <= 0) {
get().removeItem(id);
return;
}
set((state) => ({
items: state.items.map((item) =>
item.id === id ? { ...item, quantity } : item
)
}));
},
clearCart: () => set({ items: [] })
}))
);
// Uso - re-renders optimizados
function Cart() {
// Solo se re-renderiza si items cambia
const items = useCartStore((state) => state.items);
const totalPrice = useCartStore((state) => state.totalPrice());
const removeItem = useCartStore((state) => state.removeItem);
return (
<div>
<h2>Carrito</h2>
{items.map((item) => (
<div key={item.id}>
{item.name} - {item.quantity}x ${item.price}
<button onClick={() => removeItem(item.id)}>Eliminar</button>
</div>
))}
<p>Total: ${totalPrice.toFixed(2)}</p>
</div>
);
}
function AddToCartButton({ product }) {
// ¡Este componente NO se re-renderiza cuando el carrito cambia!
const addItem = useCartStore((state) => state.addItem);
return <button onClick={() => addItem(product)}>Agregar al Carrito</button>;
}
Middleware y Extensiones
1. Persist - LocalStorage Automático
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
const useSettingsStore = create(
persist(
(set) => ({
theme: 'light',
language: 'es-ES',
notifications: true,
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
toggleNotifications: () =>
set((state) => ({ notifications: !state.notifications }))
}),
{
name: 'app-settings',
storage: createJSONStorage(() => localStorage),
// Migración de versiones antiguas
version: 1,
migrate: (persistedState, version) => {
if (version === 0) {
// Migra de v0 a v1
return { ...persistedState, notifications: true };
}
return persistedState;
}
}
)
);2. Immer - Inmutabilidad Simplificada
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
const useTodoStore = create(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
// ¡Con immer, puedes "mutar" directamente!
state.todos.push({
id: Date.now(),
text,
completed: false
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed; // ¡Mutación directa!
}
}),
removeTodo: (id) =>
set((state) => {
const index = state.todos.findIndex((t) => t.id === id);
if (index !== -1) {
state.todos.splice(index, 1); // ¡Mutación directa!
}
})
}))
);3. Subscriptions - React Más Allá de React
// ¡Zustand funciona fuera de componentes React!
import { useUserStore } from './stores/user';
// Subscribe a cambios
const unsubscribe = useUserStore.subscribe(
(state) => state.user,
(user, previousUser) => {
console.log('Usuario cambió:', { user, previousUser });
// Analytics tracking
if (user && !previousUser) {
analytics.track('User Logged In', { userId: user.id });
}
}
);
// Accede al estado fuera de componente
const currentUser = useUserStore.getState().user;
console.log(currentUser);
// Actualiza estado fuera de componente
useUserStore.getState().login('user@example.com', 'password');
// Cleanup
unsubscribe();
Zustand vs. Otras Soluciones
Comparación de Bundle Size
Redux + Redux Toolkit: ~10KB
MobX: ~16KB
Recoil: ~14KB
Jotai: ~3KB
Zustand: ~1KB ✅
// Zustand + Persist + DevTools: ~3KB (¡aún menor que las alternativas!)Comparación de Rendimiento
// Benchmark: 10,000 updates en lista de 1000 ítems
// Redux: ~850ms
// - Re-renders innecesarios
// - Normalización manual necesaria
// Context API: ~1200ms
// - Todos los consumidores se re-renderizan
// Zustand: ~420ms ✅
// - Re-renders optimizados por selector
// - Zero overheadComparación de Developer Experience
// Redux Toolkit (mejor DX de Redux)
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 }
}
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
// Todavía necesita store, provider, etc...
// ~30 líneas de código
// Zustand
const useCounter = create((set) => ({
value: 0,
increment: () => set((s) => ({ value: s.value + 1 })),
decrement: () => set((s) => ({ value: s.value - 1 }))
}));
// ~6 líneas, mismo resultado ✅Cuándo NO Usar Zustand
✅ Usa Zustand cuando:
- Necesitas state global simple
- Quieres rendimiento optimizado
- Prefieres código mínimo
- Trabajas en proyecto React moderno
⚠️ Considera alternativas cuando:
- El equipo ya domina Redux y el proyecto es grande (costo de migración)
- Necesitas time-travel debugging complejo
- Aplicación legada con fuerte acoplamiento a Redux
Si quieres dominar React y state management moderno, te recomiendo el artículo Vue Vapor Mode: La Revolución que Elimina el Virtual DOM donde exploramos optimizaciones de rendimiento en frameworks modernos.
¡Vamos a por ello! 🦅
📚 Domina React y JavaScript Moderno
Zustand representa el futuro minimalista del state management, pero dominar React y JavaScript es esencial para aprovechar herramientas modernas al máximo.
Opciones de inversión:
- $9.90 USD (pago único)
💡 Material actualizado con las mejores prácticas del mercado

