Estado global de cliente
El estado que varias vistas comparten de verdad: tema, usuario autenticado, carrito. Elevar del prop drilling al ancestro común, cuándo pasar a un store (Zustand, Jotai, nanostores) y por qué el global es un pasivo, no un valor por defecto.
La segunda clase es la que todo el mundo abusa. Estado global de cliente es el dato que vive en el navegador y que varias vistas distantes necesitan de verdad: el tema claro u oscuro, el usuario autenticado, el contenido del carrito. La palabra clave es “de verdad”. La mayoría de lo que acaba en un store global no es genuinamente compartido: es estado local que alguien elevó por pereza. Esta lección traza la frontera y da el criterio para cruzarla solo cuando toca.
- Distinguir el estado global-porque-compartido del global-porque-cómodo.
- Elevar con método: del prop drilling al primer ancestro común.
- Saber cuándo el Contexto se queda corto y toca un store dedicado.
- Conocer los stores de 2026 (Zustand, Jotai, nanostores) y el papel de los selectores.
Qué hace global a un estado
Un estado es global de cliente cuando su ámbito cruza vistas que no comparten un ancestro cercano y su tiempo de vida excede al de cualquiera de ellas. El tema de la app lo leen la barra superior, el pie y cada tarjeta; el usuario autenticado lo consultan el menú, el perfil y las rutas protegidas. Ninguna de esas piezas es dueña del dato; todas lo consumen. Ese es el patrón: muchos lectores dispersos, una sola fuente de la verdad.
El contraejemplo es lo importante. La pestaña activa de un panel de ajustes la lee solo ese panel: no es global, aunque “se sienta” importante. El criterio no es la relevancia del dato, sino cuántas vistas independientes lo necesitan. Igual de decisivo es el tiempo de vida: el estado global suele sobrevivir al montaje y desmontaje de las vistas que lo consumen —el carrito sigue lleno aunque cierres y reabras el panel de productos—, mientras que el local muere con su componente. Ámbito ancho más vida larga: esa es la firma.
flowchart TD Q[un estado en un componente] --> A[lo necesita otra vista lejana] A -->|no| L[dejalo local] A -->|si| B[cuantas vistas lo comparten] B -->|dos hermanas| C[eleva al ancestro comun] B -->|muchas y dispersas| D[store global de cliente] style L fill:#a6e3a1,color:#11111b style C fill:#89b4fa,color:#11111b style D fill:#cba6f7,color:#11111b
La dirección segura del refactor es de dentro hacia fuera: un estado nace local y sube solo cuando aparece una necesidad real de compartir. Bajar del store a lo local es fácil; subir antes de tiempo y luego desenredar el acoplamiento es caro. Ante la duda, déjalo abajo: es mucho más barato elevar un estado el día que de verdad haga falta que rescatarlo de un store global al que nunca debió llegar.
Elevar: del prop drilling al Contexto
El primer paso, cuando dos hermanos deben compartir un dato, no es un store: es elevar el estado a su ancestro común y pasarlo hacia abajo. Funciona hasta que la distancia crece y aparece el prop drilling — pasar una prop por cinco niveles que no la usan solo para que llegue al sexto. Ahí entra el Contexto:
const TemaContext = createContext<Tema>('claro');
function App() {
const [tema, setTema] = useState<Tema>('claro'); // la fuente de la verdad
return (
<TemaContext.Provider value={tema}>
<Layout />
</TemaContext.Provider>
);
}
// cualquier descendiente, sin drilling:
const tema = useContext(TemaContext);
Antes de saltar al Contexto, recuerda una alternativa que casi todos olvidan: la composición. Buena parte del prop drilling se disuelve pasando JSX como children o como props de render, de modo que el estado se queda arriba y los componentes intermedios nunca ven la prop que solo estaban reenviando. El Contexto es para cuando el árbol de consumidores es amplio e impredecible; la composición, para cuando el problema es solo de profundidad.
Un error caro de 2026: usar el Contexto como si fuera un gestor de estado optimizado. No lo es. Cuando el value del Provider cambia, React vuelve a renderizar todos los consumidores, sin importar qué parte del valor usen. Para un tema que cambia una vez cada mucho, es perfecto. Para un carrito que se actualiza en cada clic y que consumen decenas de componentes, el Contexto se convierte en un cuello de botella. El Contexto resuelve la distribución (evita el drilling), no la granularidad (quién se entera de qué). Cuando necesitas suscripciones finas, toca un store.
Existe un punto intermedio: los patrones de context selector (la librería use-context-selector, y las mejoras de selección que React va incorporando) permiten suscribirse a una porción del valor de un contexto sin re-renderizar en cada cambio. Pero cuando llegas ahí, suele ser más honesto reconocer que lo que tienes es un store, y usar uno de verdad.
Los stores de 2026 y los selectores
Cuando el estado es global de verdad y cambia a menudo, un store dedicado gana. La ola post-Redux domina: Zustand (un store minimalista, sin Provider), Jotai (estado por átomos componibles) y nanostores (diminuto, ideal para islas de Astro y multi-framework). Todos comparten el rasgo que el Contexto no tiene: suscripción por selector, cada componente escucha solo el trozo que usa.
import { create } from 'zustand';
const useCarrito = create<CarritoState>((set) => ({
items: [],
add: (i) => set((s) => ({ items: [...s.items, i] })),
}));
// este componente SOLO se re-renderiza si cambia el numero de items
const total = useCarrito((s) => s.items.length);
El selector (s) => s.items.length es la clave: si añades una propiedad no relacionada al store, este componente no se entera. Esa granularidad es exactamente lo que convierte a un store en algo escalable y al Contexto en algo que hay que trocear a mano.
Estos stores además componen con middleware. El de persistencia (como persist de Zustand) sincroniza el store con localStorage o IndexedDB de forma transparente, y así el carrito o el tema sobreviven a una recarga. Cuidado: eso convierte parte de tu estado global en estado persistido, con sus propios problemas de hidratación en SSR (Nivel 36). Buen recordatorio de que las clases no son cajones estancos: un mismo dato puede ser global de cliente y, además, persistido.
Cómo elegir entre Contexto, Zustand y átomos
Las tres opciones cubren estado compartido, pero encajan en problemas distintos. El Contexto sirve para valores estables y de baja frecuencia (tema, locale, usuario) que muchos leen pero pocos escriben. Zustand brilla con estado de dominio que cambia a menudo y se lee por selectores. Jotai invierte el modelo: en vez de un store central, compones átomos pequeños e independientes, y el estado derivado se expresa como un átomo que depende de otros.
import { atom, useAtom } from 'jotai';
const itemsAtom = atom<Item[]>([]);
const totalAtom = atom((get) => get(itemsAtom).length); // derivado, reactivo
function Contador() {
const [total] = useAtom(totalAtom); // se recomputa solo si cambia itemsAtom
return <span>{total}</span>;
}
Contexto
Distribución sin drilling para valores estables y de escritura rara. No optimiza renders: todos los consumidores se actualizan a la vez.
Zustand
Store minimalista sin Provider, con suscripción por selector. La opción por defecto para estado de dominio que cambia a menudo.
Jotai
Átomos componibles y estado derivado de grano fino. Encaja cuando el estado es una red de piezas pequeñas interdependientes.
El criterio
Baja frecuencia y distribución: Contexto. Dominio mutable central: Zustand. Grafo de derivaciones finas: Jotai.
En una arquitectura de islas como la de Astro, los frameworks conviven en la misma página pero cada isla se hidrata por separado, así que un store atado a React no sirve para compartir con una isla de Svelte o Solid. nanostores resuelve justo eso: es diminuto (menos de 1 KB), agnóstico de framework y vive fuera del árbol de componentes, de modo que varias islas heterogéneas leen y escriben el mismo estado. Cuando el estado global debe cruzar la frontera entre frameworks, esa independencia deja de ser un lujo y pasa a ser el requisito.
Hay una asimetría que casi nadie internaliza: el estado local es barato y el global es caro, y no por razones de rendimiento sino de razonamiento. Cada dato que asciende al ámbito global se convierte en algo que cualquier persona que lea cualquier parte del código debe tener en la cabeza como posible causa de cualquier comportamiento. Un store con cuarenta claves es un espacio de estados combinatorio que nadie entiende del todo. Por eso la regla no es “centraliza el estado”, sino “centraliza solo lo genuinamente compartido, y ni una clave más”. Antes de meter algo en el store, pásalo por tres filtros. Primero, ¿lo leen de verdad varias vistas independientes, o solo una y estoy anticipando? Segundo, ¿es estado de cliente, o son datos del servidor disfrazados (que van a una cache, lección 3, no al store)? Tercero, ¿debería sobrevivir a una recarga o compartirse por enlace (entonces es URL, lección 4)? Lo que sobreviva a los tres filtros —tema, sesión, carrito, preferencias de UI verdaderamente transversales— es el diminuto núcleo que merece ser global. Todo lo demás elevado al store es deuda: acopla lo que estaba suelto, ensancha la superficie de fallo y convierte el time-travel en ruido. El buen arquitecto no mide su store por lo que contiene, sino por lo poco que ha logrado dejar fuera.
- Abre tu store o tus contextos y lista cada clave; junto a cada una escribe qué vistas la leen de verdad.
- Marca las que solo lee una vista: son candidatas a bajar a estado local (lección 1).
- Marca las que en realidad son datos del servidor: sácalas del store; irán a una cache (lección 3).
- Sustituye un Contexto que cambie con frecuencia por un store con selectores y mide la caída de renders.
- Por cada clave que sobreviva a la auditoría, confirma que la leen varias vistas independientes; si no, no era global.