CodeForge

React 19 de Cero a Experto / Estado global

Zustand: estado global sin ceremonia

Teoría24 min20 XP

Cuando el estado global cambia seguido o quieres que solo re-rendericen los componentes que usan la parte que cambió, Context se queda corto. La herramienta favorita del ecosistema React para eso es ZUSTAND: un store minúsculo, sin Provider, con selectores. Y aquí no solo la vas a usar —la vas a CONSTRUIR—: tu propio mini-Zustand en ~15 líneas que corre en vivo, porque por dentro es EXACTAMENTE el store observable que hiciste en JavaScript (SC-02, M13). Luego ves la real.

El marcador del estadio, otra vez

Construye tu mini-Zustand (en vivo)

Un store de Zustand es un objeto con estado y acciones, más una forma de suscribirse. React 18 trae el hook exacto para conectarlo: useSyncExternalStore. Aquí está tu propio crearStore corriendo:

Agrega y borra gastos: Form, Lista y Total comparten el mismo bolsillo SIN un Provider ni una sola prop entre ellos. Cada uno se suscribe con un selector (s => s.gastos) y re-renderiza cuando esa parte cambia. Ese crearStore es el store observable del SC-02 (Set de oyentes, setState inmutable que avisa, subscribe que devuelve la desuscripción); useSyncExternalStore es el puente oficial de React para conectarlo. Acabas de construir el 80% de Zustand.

El Zustand real

La librería es ese mismo patrón, con una API pulida. Compara —es casi idéntico a tu store—:

zustand-real.tsx
import { create } from "zustand";

type BolsilloState = {
gastos: Gasto[];
agregar: (nombre: string, valor: number) => void;
borrar: (id: number) => void;
};

// create() ES tu crearStore: recibe (set) y devuelve estado + acciones
const useBolsillo = create<BolsilloState>((set) => ({
gastos: [],
agregar: (nombre, valor) =>
  set((s) => ({ gastos: [...s.gastos, { id: Date.now(), nombre, valor }] })),
borrar: (id) => set((s) => ({ gastos: s.gastos.filter((g) => g.id !== id) })),
}));

// en cualquier componente, sin Provider, con selector:
function Total() {
const gastos = useBolsillo((s) => s.gastos); // se suscribe SOLO a gastos
const total = gastos.reduce((sum, g) => sum + g.valor, 0);
return <p>Total: {total}</p>;
}

create((set) => ({...})) es tu crearStore; el hook que devuelve (useBolsillo) es tu useStore con el store ya incorporado; el selector (s) => s.gastos es idéntico. Zustand añade cosas útiles —middleware para persistir en localStorage, integración con Redux DevTools, subscribe fuera de React— pero el modelo mental es el que acabas de construir. Se instala con pnpm add zustand.

¿Qué ventaja tiene Zustand sobre Context para estado global que cambia seguido?

Mini-reto

Amplía tu mini-Zustand: 1) agrega al store una acción limpiar() que vacíe los gastos, y un botón que la use; 2) agrega un componente Contador que se suscriba SOLO a s => s.gastos.length y muestre el número —comprueba que funciona igual que los demás—; 3) en un comentario, explica por qué el store vive FUERA del componente App (pista: para que sobreviva y se comparta, como el Map de caché del módulo 5). Siente que es el store del SC-02 con el enchufe de React.

Qué sigue

Context y Zustand guardan "qué datos hay". Pero algunos estados no son datos sueltos sino FLUJOS con reglas: "de "cargando" solo se puede ir a "éxito" o "error", nunca a "inactivo"". Eso son las MÁQUINAS DE ESTADO, y la próxima lección las construye (tu propia mini-máquina en vivo, la lavadora del SC-02 revisitada) antes de mostrar XState.