Kodokon kodokon.com

Context: compartir sin prop drilling

Mueve datos sin prop drilling usando el context, manteniendo bajo control sus re-renders y sus límites.

8 min · 3 preguntas

Abrir esta lección en Kodokon

El prop drilling se refiere a esas props que atraviesan tres o cuatro componentes intermedios sin usarse en ellos, solo para llegar a una hoja del árbol. Cada capa que cruzan queda acoplada a datos que no le interesan. El context resuelve este problema de transporte: createContext crea el canal, un provider publica un valor y useContext lo lee desde cualquier descendiente. Ten presente el matiz: el context transporta el estado, no lo gestiona - el valor sigue viviendo en un componente, aquí el provider.

JSX
import { createContext, useMemo, useState } from "react";

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
El valor publicado se memoiza para mantener su referencia estable.

Cuando el valor del provider cambia, todos los consumidores vuelven a renderizarse, incluso los que están muy anidados - y React.memo no los protege, porque compara props, no el context. La trampa clásica lo empeora: escribir value={{ theme, setTheme }} crea un objeto nuevo en cada render del provider, de ahí una nueva referencia, de ahí re-renders en cascada aunque theme no se haya movido. Para eso sirve el useMemo anterior. Completa la configuración con un hook de acceso que falle ruidosamente fuera del provider.

JSX
import { useContext } from "react";

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === null) {
    throw new Error(
      "useTheme must be inside <ThemeProvider>"
    );
  }
  return context;
}
Un error explícito es mejor que un null silencioso.

Antes de crear un context, agota primero la opción de la composición. Si una capa intermedia no hace más que retransmitir una prop, invierte la estructura: construye el elemento en la cima del árbol, donde viven los datos, y pásalo hacia abajo mediante children o una prop dedicada. La capa intermedia se vuelve genérica y los datos ya no pasan por ningún componente que no los use.

JSX
function App({ currentUser }) {
  return (
    <Layout
      header={<TopBar user={currentUser} />}
      content={<Dashboard />}
    />
  );
}
Layout no sabe nada de currentUser: encaja elementos ya preparados.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. Un consumidor de context está envuelto en React.memo y el valor del context cambia. ¿Qué ocurre?
    • No se vuelve a renderizar: React.memo bloquea todas las actualizaciones
    • Se vuelve a renderizar: React.memo compara props, no valores de context
    • Se vuelve a renderizar solo si sus props también cambian
  2. ¿Por qué memoizar el objeto que se pasa a value en un provider?
    • Para acelerar la creación del objeto en cada render
    • Porque el provider exige un valor inmutable
    • Para que la referencia solo cambie cuando cambian los datos, evitando re-renders innecesarios de los consumidores
  3. ¿Cuál es la principal limitación del context para un estado que se actualiza con mucha frecuencia?
    • Sin selectores: cada actualización vuelve a renderizar a todos los consumidores, incluso los que solo usan una parte del valor
    • El context es asíncrono y retrasa las actualizaciones
    • El context solo puede almacenar valores primitivos