useContext y la pila de proveedores
¿Por qué useContext no ocupa lugar en la cadena de hooks?
Al terminar vas a poder explicar
- Explicar cómo llega el valor del contexto a un componente hondo
- Predecir qué componentes se re-renderizan al cambiar un Provider
- Evitar el objeto nuevo en la prop value, que es el error clásico
Todos los hooks que vimos hasta ahora viven en la cadena enlazada del fiber: useState, useEffect, useMemo. Cada uno ocupa una posición y por eso el orden importa.
useContext es distinto. No ocupa lugar en la cadena. Y esa sola diferencia explica casi todo su comportamiento.
Una pila, no una cadena
Cuando React baja por el árbol y encuentra un Provider, empuja su value en una pila asociada a ese contexto. Cuando termina de procesar toda esa rama y sube, lo saca.
1// beginWork, al bajar:2if (esProvider(fiber)) {3 contexto._pila.push(fiber.pendingProps.value);4}5 6// completeWork, al subir:7if (esProvider(fiber)) {8 contexto._pila.pop();9}10 11// useContext, en cualquier punto:12return contexto._pila.at(-1) ?? contexto.valorPorDefecto;Por eso useContext siempre lee el Provider más cercano hacia arriba: es el último que se empujó y todavía no se sacó. Y por eso podés anidar Providers del mismo contexto y cada rama ve el suyo.
El valor empujándose y bajando por el árbol
AGENDADOpaso 1 / 144Sin hooks en este paso.
Árbol de fibers
- <HostRoot>
Salida
Nada en el host todavía. React no commiteó ningún cambio.
createRoot().render()
Montaje inicial. No hay árbol previo, así que todo se va a crear desde cero.
VER EL CÓDIGO QUE SE ESTÁ TRAZANDO
1const TemaCtx = createContext("claro");2 3function BotonTematizado() {4 const tema = useContext(TemaCtx); // lee el Provider más cercano5 return <button>soy {tema}</button>;6}7 8function App() {9 const [tema, setTema] = useState("oscuro");10 11 return (12 <TemaCtx.Provider value={tema}>13 <Panel>14 <BotonTematizado />15 </Panel>16 </TemaCtx.Provider>17 );18}Con el foco acá: ← → un paso · espacio reproduce · Inicio/Fin van a los extremos.
Buscá los pasos Tema.Provider · push y el useContext(Tema). Fijate que el hook lee el valor sin agregar ningún nodo a la cadena de <BotonTematizado> — su lista de hooks está vacía.
Y ahora el problema: los re-renders
Cuando el value de un Provider cambia, todos sus consumidores se re-renderizan. Sin excepción, sin comparación superficial, sin importar si usan la parte del valor que cambió.
Combinado con lo que ya sabés sobre identidad de referencias, esto produce el error más común del contexto:
1// ✗ Objeto nuevo en cada render del proveedor → todos los consumidores re-renderizan.2function App() {3 const [usuario, setUsuario] = useState(null);4 return (5 <Auth.Provider value={{ usuario, setUsuario }}>6 <TodaLaApp />7 </Auth.Provider>8 );9}10 11// ✓ Referencia estable mientras usuario no cambie.12function App() {13 const [usuario, setUsuario] = useState(null);14 const valor = useMemo(() => ({ usuario, setUsuario }), [usuario]);15 return (16 <Auth.Provider value={valor}>17 <TodaLaApp />18 </Auth.Provider>19 );20}Ojo: setUsuario es estable — sale de la cola del hook y no cambia entre renders — así que sólo hace falta usuario en las dependencias.
Partir el contexto en dos
Hay un patrón que resuelve el caso más común y que casi nadie usa: separar lo que cambia seguido de lo que no cambia nunca.
1const EstadoCtx = createContext(null); // cambia seguido2const AccionesCtx = createContext(null); // no cambia nunca3 4function Proveedor({ children }) {5 const [estado, despachar] = useReducer(reducer, inicial);6 7 // despachar es estable de por vida: este value no cambia jamás.8 return (9 <AccionesCtx.Provider value={despachar}>10 <EstadoCtx.Provider value={estado}>{children}</EstadoCtx.Provider>11 </AccionesCtx.Provider>12 );13}Ahora un componente que sólo despacha acciones — un botón, un formulario — consume AccionesCtx y nunca se re-renderiza cuando el estado cambia. Sin memo, sin selectores, sin librerías.
Antes de seguir · predecí
Un Provider está en la raíz con value={{ tema, usuario }}. Cambiás el tema. ¿Qué se re-renderiza?
Lo que te llevás
El contexto viaja por una pila que el reconciler empuja al bajar y saca al subir, no por la cadena de hooks. Cambiar el value re-renderiza a todos los consumidores por identidad de referencia, así que estabilizalo. Y si el estado cambia seguido, partí el contexto en uno de estado y otro de acciones.
Antes de marcarla, comprobá
- ¿Podés explicar explicar cómo llega el valor del contexto a un componente hondo?
- ¿Podés explicar predecir qué componentes se re-renderizan al cambiar un Provider?
- ¿Podés explicar evitar el objeto nuevo en la prop value, que es el error clásico?