CarlosSeijas
← Volver a los blogs

¿Qué es Zustand? Guía completa del state manager minimalista

Código
ZustandReactState ManagementJavaScriptEstadoReduxFrontend
¿Qué es Zustand? Guía completa del state manager minimalista

¿Has pasado horas configurando Redux solo para manejar un simple contador? Yo sí, y en ese momento pensé "tiene que haber una forma más fácil de hacer esto". Entre actions, reducers, dispatchers y todo ese boilerplate, a veces sientes que necesitas un PhD en Redux para cambiar un simple estado. Pues déjame contarte sobre Zustand, la librería que me hizo redescubrir lo simple que puede ser el manejo de estado.

Zustand: el gestor de estado que no sabías que necesitabas

Zustand significa "estado" en alemán, y es exactamente lo que promete: una forma súper directa de manejar el estado global de tu aplicación React sin toda esa ceremonia innecesaria. Es como comparar escribir una carta formal con enviar un WhatsApp - ambos comunican, pero uno es mucho más directo.

Lo creó el equipo de Poimandres (los mismos genios detrás de React Spring), y cuando lo probé por primera vez me quedé pensando "¿en serio es así de simple?". Con apenas 2.9KB gzipped, es como tener el poder de Redux en el tamaño de una imagen pequeña.

Por qué me enamoré de Zustand

Cómo funciona esta belleza

Instalación (lo más fácil del mundo)

npm install zustand

Y crear tu primer store es tan simple que da risa:

import { create } from 'zustand'

const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}))

Usarlo en React (sin drama)

import React from 'react'
import { useCounterStore } from './store'

function Counter() {
  const { count, increment, decrement, reset } = useCounterStore()

  return (
    <div>
      <p>Contador: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

¿Ves qué limpio queda? Sin providers, sin dispatchers, sin dolor de cabeza.

Ejemplos reales que he usado en producción

1. Manejo de usuarios (mi favorito)

import { create } from 'zustand'

const useUserStore = create((set, get) => ({
  users: [],
  loading: false,
  error: null,

  fetchUsers: async () => {
    set({ loading: true, error: null })
    try {
      const response = await fetch('/api/users')
      const users = await response.json()
      set({ users, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  },

  addUser: (user) => set((state) => ({
    users: [...state.users, { ...user, id: Date.now() }]
  })),

  removeUser: (id) => set((state) => ({
    users: state.users.filter(user => user.id !== id)
  })),

  updateUser: (id, updates) => set((state) => ({
    users: state.users.map(user => 
      user.id === id ? { ...user, ...updates } : user
    )
  })),
}))

2. Carrito de compras (que funciona como relojito)

const useCartStore = create((set, get) => ({
  items: [],
  total: 0,

  addItem: (product) => {
    const { items } = get()
    const existingItem = items.find(item => item.id === product.id)
    
    if (existingItem) {
      set((state) => ({
        items: state.items.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        )
      }))
    } else {
      set((state) => ({
        items: [...state.items, { ...product, quantity: 1 }]
      }))
    }
    
    get().calculateTotal()
  },

  removeItem: (id) => {
    set((state) => ({
      items: state.items.filter(item => item.id !== id)
    }))
    get().calculateTotal()
  },

  calculateTotal: () => {
    const { items } = get()
    const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
    set({ total })
  },

  clearCart: () => set({ items: [], total: 0 }),
}))

3. Temas y configuración (para apps que se adaptan)

const useThemeStore = create((set) => ({
  theme: 'light',
  fontSize: 16,
  language: 'es',

  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  })),

  setFontSize: (size) => set({ fontSize: size }),
  setLanguage: (lang) => set({ language: lang }),

  resetSettings: () => set({
    theme: 'light',
    fontSize: 16,
    language: 'es'
  }),
}))

La diferencia brutal entre Zustand y Redux

Redux (el camino difícil)

// Actions
const INCREMENT = 'INCREMENT'
const increment = () => ({ type: INCREMENT })

// Reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 }
    default:
      return state
  }
}

// Store
const store = createStore(counterReducer)

// Provider setup
<Provider store={store}>
  <App />
</Provider>

Zustand (el camino sensato)

const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

¿En serio necesito decir más? Es como comparar armar un mueble de IKEA con instrucciones en chino vs con instrucciones en tu idioma nativo.

La comparación que todos necesitamos

| Característica | Zustand | Redux | |----------------|---------|--------| | Setup | Mínimo | Complejo | | Boilerplate | Casi nulo | Abundante | | TypeScript | Nativo | Requiere configuración | | Performance | Excelente | Buena | | Tamaño | 2.9KB | 47KB+ | | Curva aprendizaje | Suave | Empinada |

Trucos avanzados que he aprendido usando Zustand

1. Subscripciones selectivas (evita re-renders innecesarios)

// Solo se re-renderiza cuando cambia el nombre
const name = useUserStore(state => state.name)

// Solo se re-renderiza cuando cambia el email
const email = useUserStore(state => state.email)

2. Middleware personalizado (persistir datos es súper fácil)

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }),
    {
      name: 'counter-storage',
    }
  )
)

3. Combinando stores (cuando necesitas más organización)

const useAuthStore = create((set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
}))

const useAppStore = create((set, get) => ({
  ...useAuthStore.getState(),
  
  // Acceder a otros stores
  isUserAdmin: () => {
    const user = get().user
    return user?.role === 'admin'
  },
}))

Lo que he aprendido usando Zustand en proyectos reales

1. Mantén tus stores enfocados

// ✅ Bueno - Store específico y enfocado
const useUserStore = create((set, get) => ({
  // Estado
  users: [],
  loading: false,
  
  // Acciones
  fetchUsers: async () => { /* ... */ },
  addUser: (user) => { /* ... */ },
}))

// ❌ Malo - Store monolítico
const useGlobalStore = create((set) => ({
  users: [],
  products: [],
  orders: [],
  notifications: [],
  // ... demasiado estado en un solo lugar
}))

2. Usa selectores específicos (performance matters)

// ✅ Bueno - Selector específico
const userName = useUserStore(state => state.user?.name)

// ❌ Malo - Selector que retorna todo
const userStore = useUserStore()

3. Manejo de estado asíncrono que no falla

const useDataStore = create((set, get) => ({
  data: null,
  loading: false,
  error: null,

  fetchData: async () => {
    set({ loading: true, error: null })
    try {
      const data = await api.getData()
      set({ data, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  },
}))

Mi experiencia personal con Zustand

Cuando descubrí Zustand, llevaba años usando Redux y estaba acostumbrado a todo el ritual. Pensaba que esa complejidad era necesaria, que era "la forma correcta" de hacer state management. Pero después de usar Zustand en mi primer proyecto, me di cuenta de que había estado complicándome la vida innecesariamente.

Lo importante es que Zustand no es solo "Redux más simple" - es una mentalidad diferente. En lugar de pensar en acciones y reductores, piensas directamente en estado y funciones que lo modifican. Es más natural, más intuitivo.

En mi experiencia, los equipos adoptan Zustand súper rápido. No necesitas explicar conceptos abstractos ni patrones complejos. Le dices a alguien "crea una función, modifica el estado, úsalo en tu componente" y ya está programando.

Ojo que esto no significa que Redux sea malo. Para aplicaciones súper complejas con flujos de datos intrincados, Redux sigue siendo útil. Pero para la mayoría de aplicaciones, Zustand es como tener un superpoder: haces más con menos código, menos configuración, menos dolor de cabeza.

Una cosa que me sorprendió es lo bien que funciona con TypeScript. No necesitas crear interfaces separadas para actions y state, no necesitas configurar nada extra - simplemente funciona. Es como si hubiera sido diseñado por developers que realmente usan TypeScript en el día a día.

Desde que empecé a usar Zustand, he vuelto a disfrutar el manejo de estado. Ya no es esa parte tediosa del proyecto que postergás hasta el final; es algo que puedes implementar rápido y seguir construyendo funcionalidades.

¿Has usado alguna vez Redux y sentiste que era demasiado ceremonioso para lo que necesitabas? ¿O tienes curiosidad por probar Zustand en tu próximo proyecto? Me encantaría saber qué piensas sobre estos gestores de estado más minimalistas.

Comentarios

Posts relacionados