DevPath · Aprende a programar ESPTEN

Datos y estado global

Estado global: más allá de Context

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 Provider cambia, 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?

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.)

Pon esto en práctica

DevPath es un curso práctico: aquí lees la teoría; en la app la pones en práctica con ejercicios que se ejecutan de verdad, sin conexión.

Empezar gratis en la app →
← Data fetching moderno: TanStack Query y SWRYou Might Not Need an Effect →