Saltar al contenido

useContext y el patrón useX que exige Provider

useContext consume el canal: devuelve el valor del Provider más cercano o el default, trepando por el árbol de owners. Debe leerse en el cuerpo del componente, donde hay owner. Y el patrón profesional no es llamarlo suelto, sino envolverlo en un hook useX que valida que existe Provider y falla con un mensaje claro cuando no.

useContext: leer el canal

useContext toma el objeto que devolvió createContext y devuelve el valor que corresponde a tu posición en el árbol. Por dentro lee el owner actual y trepa de padre en padre buscando el primer owner que fijó ese id; si lo encuentra, devuelve ese valor; si llega a la raíz sin hallarlo, devuelve el defaultValue.

TSX
import { useContext } from "solid-js";function Boton() {  const tema = useContext(TemaContext); // "oscuro" si hay Provider, "claro" si no  return <button classList={{ oscuro: tema === "oscuro" }}>Aceptar</button>;}

Como el componente corre una sola vez, esta lectura ocurre una vez. No es un coste por render —no hay renders— sino una búsqueda única en el montaje. El resultado lo capturas en una constante y lo usas en el JSX y en los efectos con normalidad.

Dónde se puede llamar: el owner tiene que estar presente

La trampa número uno: useContext necesita un owner actual para saber desde dónde trepar. Ese owner existe mientras se ejecuta el cuerpo del componente y dentro de sus efectos. No existe dentro de un manejador de evento ni en un callback asíncrono suelto: cuando el usuario hace clic, el manejador corre fuera del owner, así que un useContext ahí dentro no trepa por ningún sitio y devuelve el default —casi nunca lo que querías—.

TSX
function Perfil() {  const sesion = useContext(SesionContext); // BIEN: en el cuerpo, hay owner  const alGuardar = () => {    // MAL: aqui no hay owner; useContext(SesionContext) devolveria el default    api.guardar(sesion.usuario); // usa la constante ya leida arriba  };  return <button onClick={alGuardar}>Guardar</button>;}

Lo mismo vale para un setTimeout, un .then o cualquier callback que corra en un tick futuro: para entonces el owner que había durante el montaje ya no es el actual, y useContext no encuentra el canal. La regla operativa es simple: lee el contexto en el cuerpo del componente y cierra sobre el resultado. Nunca dentro del manejador ni de un callback diferido. Es la misma lección que ya viste con la reactividad —el rastreo necesita un contexto de ejecución— aplicada al owner que resuelve el canal.

El agujero del default ausente

Cuando creas el contexto sin valor por defecto —lo habitual en contextos obligatorios— y un componente lo consume sin Provider encima, useContext devuelve undefined. El desastre no ocurre ahí, sino una línea después, cuando lees una propiedad de ese undefined y la aplicación revienta con un cannot read properties of undefined a kilómetros de la causa real, que era “olvidé envolver en el Provider”.

TSX
const SesionContext = createContext<Sesion>(); // sin default -> Sesion | undefinedfunction Cabecera() {  const sesion = useContext(SesionContext);  return <span>{sesion.usuario.nombre}</span>; // si falta el Provider: crash aqui}

Lo insidioso es la distancia entre causa y síntoma. El undefined no falla al obtenerlo; falla cuando alguien lee un campo suyo, que puede estar varios componentes más allá. El stack trace señala a Cabecera, pero el error real vive en quien la renderizó sin envolverla en el Provider. En una app grande, esa distancia se paga en minutos de depuración por un olvido de una sola línea.

Este es el momento en que el ingeniero con oficio deja de consumir el contexto directamente y construye una guarda.

El patrón useX: un hook que exige el Provider

En lugar de esparcir useContext(SesionContext) por toda la app, se escribe una función useSesion que lo llama, comprueba que hay valor y, si no lo hay, lanza un error explícito. Ese hook pasa a ser la única puerta de entrada al contexto.

TSX
export function useSesion() {  const ctx = useContext(SesionContext);  if (!ctx) {    throw new Error("useSesion debe usarse dentro de un SesionProvider");  }  return ctx; // a partir de aqui, el tipo ya no incluye undefined}

Este patrón compra cuatro cosas a la vez. Un fallo temprano y legible: el error apunta al problema real —falta el Provider— en vez de a un síntoma lejano. Encapsulación: puedes no exportar SesionContext y exportar solo useSesion, de modo que el único acceso posible al canal sea el hook, imposibilitando saltarse la validación. Un punto único donde vive el invariante y donde mañana añadirás logging, métricas o valores derivados sin tocar a los consumidores. Y —lo verás en detalle en la próxima lección— estrechamiento de tipo: tras el throw, TypeScript sabe que el valor ya no es undefined.

TSX
// El consumidor ya no toca el contexto: solo el hook, sin comprobar nada.function Avatar() {  const sesion = useSesion(); // garantizado y no nulo  return <img src={sesion.usuario.avatar} alt={sesion.usuario.nombre} />;}