Recordatorio: Context
useContext resuelve el prop drilling: en vez de pasar una prop por diez
niveles, un Provider ofrece un valor y cualquier descendiente lo lee. Es
perfecto para datos que cambian poco: el tema (claro/oscuro), el idioma, el
usuario autenticado.
Dónde se queda corto Context
Context no fue diseñado como gestor de estado de alto rendimiento. Su limitación principal son los re-renders:
Cuando el valor de un
Providercambia, todos los componentes que consumen ese contexto se vuelven a renderizar, aunque solo les interese una parte del valor.
Si metes en un único contexto un objeto grande ({ usuario, carrito, ajustes, notificaciones }) y cambia una propiedad, todos los consumidores se
repintan. Context no tiene selectores: no puedes suscribirte solo a
carrito.total e ignorar el resto. En una app con estado que cambia a menudo,
esto se traduce en renders innecesarios y la solución a mano (partir en muchos
contextos, memoizar) se vuelve aparatosa.
Ahí entran las librerías de estado global, cuya gran ventaja son los selectores: cada componente se suscribe solo al trozo de estado que usa.
Zustand
Zustand es minimalista. Creas un store con create pasándole una función
que define estado y acciones:
import { create } from "zustand";
const useContador = create((set) => ({
cuenta: 0,
incrementar: () => set((s) => ({ cuenta: s.cuenta + 1 })),
reset: () => set({ cuenta: 0 }),
}));
El propio store es un hook. Lo usas con un selector para leer solo lo que necesitas:
function Marcador() {
// Se re-renderiza SOLO si cambia 'cuenta'
const cuenta = useContador((s) => s.cuenta);
return <p>Cuenta: {cuenta}</p>;
}
function Boton() {
// Este solo lee la acción: no se re-renderiza cuando cambia 'cuenta'
const incrementar = useContador((s) => s.incrementar);
return <button onClick={incrementar}>+1</button>;
}
No hace falta Provider que envuelva la app, no hay boilerplate y el selector
(s) => s.cuenta evita los re-renders que sufriría Context.
Redux Toolkit
Redux es el clásico del estado global predecible: un único store, el estado solo cambia despachando acciones que pasan por reducers puros. Redux Toolkit (RTK) es la forma oficial y moderna de usarlo, y reduce muchísimo el código repetitivo gracias a los slices:
import { configureStore, createSlice } from "@reduxjs/toolkit";
const contadorSlice = createSlice({
name: "contador",
initialState: { cuenta: 0 },
reducers: {
incrementar: (state) => { state.cuenta += 1; }, // Immer: "mutas" un borrador
reset: (state) => { state.cuenta = 0; },
},
});
export const { incrementar, reset } = contadorSlice.actions;
export const store = configureStore({
reducer: { contador: contadorSlice.reducer },
});
En los componentes lees con useSelector (con su selector, como en Zustand)
y despachas acciones con useDispatch:
import { useSelector, useDispatch } from "react-redux";
function Marcador() {
const cuenta = useSelector((s) => s.contador.cuenta);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(incrementar())}>
Cuenta: {cuenta}
</button>
);
}
Dentro de un reducer de RTK parece que mutas el estado
(state.cuenta += 1), pero por debajo usa Immer para producir un nuevo
estado inmutable de forma segura.
¿Cuál elegir?
- Context: datos globales que cambian poco (tema, idioma, sesión). Viene con React, sin dependencias.
- Zustand: estado de cliente que cambia a menudo, con poco boilerplate y selectores. Excelente punto medio.
- Redux Toolkit: apps grandes que se benefician de un flujo muy estructurado, DevTools potentes, middlewares y convenciones de equipo.
Y recuerda: para datos del servidor, ninguno de estos; usa TanStack Query o SWR. El estado global es para el estado del cliente.
(Zustand y Redux se instalan como dependencias; aquí los vemos de forma conceptual.)