Estado compartido: el problema que resuelve un store
En la semana 12 construimos un login con useState, guardamos el token en localStorage y protegimos las rutas con un AuthGuard. Funciona, pero cada pantalla que necesita saber quién es el usuario tiene que leer localStorage por su cuenta, y ningún componente se entera cuando la sesión cambia.
Antes de instalar cualquier librería conviene entender el problema con precisión. Una herramienta de estado se elige bien solo cuando sabes qué dolor te quita. En esta lección vamos de lo más simple (useState) hasta el punto donde un store global deja de ser un lujo.
1. Qué es el estado
El estado (state) es la información que cambia con el tiempo y de la que depende lo que ves en pantalla. Cuando el estado cambia, React vuelve a dibujar la interfaz.
No todo el estado es del mismo tipo, y mezclarlos es la fuente de la mayoría de los enredos:
| Tipo | Ejemplo en nuestra app | Dónde conviene vivir |
|---|---|---|
| Local de un componente | Texto escrito en el input del login, si un menú está abierto | useState dentro del componente |
| Compartido entre componentes | Usuario autenticado, notificaciones, carrito, tema visual | Un store (lo que veremos esta semana) |
| Del servidor (server state) | Lista de usuarios que devuelve GET /users | Una capa de datos (fetch, caché); no es lo mismo que el estado de UI |
| De la URL | Filtros de búsqueda, página actual, id de la ruta | La propia URL (searchParams, params) |
Empieza siempre con el estado más local posible. Súbelo solo cuando dos componentes que no son padre e hijo necesiten el mismo dato.
2. Cómo viaja el estado en React
Recorre el árbol de componentes de una página. Verás dónde vive el estado, cómo se sube al ancestro común (lifting state up), por qué aparece el prop drilling y qué logra (y qué no) Context.
Lo esencial de ese recorrido:
- Los datos solo bajan por el árbol (de padre a hijo, como
props). No hay un camino lateral entre ramas. - Subir el estado al ancestro común resuelve el primer problema, pero obliga a pasar
propspor componentes que no las usan (prop drilling). - Context da un salto directo, pero cuando cambia su
valuese vuelven a renderizar todos los consumidores, aunque solo les interese una parte.
3. Un problema concreto: la sesión del usuario
Piensa en la aplicación del caso de estudio de la semana 12. Necesitan la sesión:
- El
AuthGuard, para decidir si deja entrar. - La barra de navegación, para mostrar el nombre y el botón "Cerrar sesión".
- El dashboard, para pedir
GET /userscon el token. - Cualquier formulario que firme una petición.
Con lo que sabemos hasta ahora hay tres salidas, todas con costo:
// 1. Props: el dato baja por todos los intermedios (prop drilling)
<AppLayout user={user}>
<Navbar user={user} /> {/* Navbar solo lo reenvía */}
</AppLayout>
// 2. Context: un Provider y useContext, pero todo consumidor se re-renderiza
<AuthContext.Provider value={{ user, token, login, logout }}>
<App />
</AuthContext.Provider>
// 3. localStorage directo (lo que hicimos en la semana 12): nadie se entera del cambio
const token = session.getToken();
Ninguna es incorrecta. Lo que pasa es que a medida que la app crece, cada una acumula deuda: props repetidas, re-renders que no se ven, o pantallas que no se actualizan cuando cierras sesión en otra parte de la app.
4. Qué es un store global
Un store es un objeto que vive fuera del árbol de componentes y que guarda dos cosas juntas:
- El estado compartido (
user,token,notifications). - Las acciones que lo modifican (
login(),logout(),addNotification()).
Cualquier componente se suscribe a la parte que le interesa y React lo vuelve a dibujar solo cuando esa parte cambia.
Analogía: el tablero de anuncios de la facultad. Cualquiera puede ir a leer el aviso que le importa, y cuando coordinación cambia un aviso, solo se entera quien estaba mirando ese aviso. Nadie le lleva el papel de mano en mano a cada salón.
Lo que cambia frente a lo anterior:
| Antes | Con un store |
|---|---|
El dato baja por props | El componente lo lee directo del store |
| El cambio sube por callbacks | El componente llama la acción del store |
| Context re-renderiza a todos | Cada componente se suscribe solo a lo que usa (selectores) |
| El estado vive en el árbol | El estado vive afuera, sobrevive a que un componente se desmonte |
Cuándo NO hace falta
Un store global no es la respuesta a todo. No lo uses para:
- El valor de un input mientras el usuario escribe (es estado local).
- Datos que vienen del servidor y se vuelven a pedir en cada pantalla (eso es server state; un store puede guardarlos, pero no sustituye una estrategia de caché).
- Algo que solo usan un componente y sus hijos directos.
5. Mapa de la semana
Esta semana recorremos el camino en este orden:
Concepto: el store y su flujo
Qué es Zustand, cómo se ve un store, qué es set, qué es un selector y cómo se compara con Redux y Context. Ver Conceptos de Zustand.
Configuración en Next.js
Instalación, tipado con TypeScript, ubicación del store por feature, 'use client' y el cuidado con el servidor. Ver Configuración de Zustand.
Capacidades
Selectores, useShallow, acciones asíncronas, middleware (devtools, persist, immer) y uso fuera de React. Ver Capacidades de Zustand.
Caso práctico
Migramos la sesión de la semana 12 a un store con persistencia y construimos un sistema de notificaciones. Ver Caso práctico.