createContext y Provider: proveer un valor por el árbol
createContext fabrica un canal —un objeto con id, Provider y valor por defecto— por el que un valor desciende por el árbol de owners sin pasar por props. El Provider fija ese valor para un subárbol, no crea nodo en el DOM y lo hace una sola vez: entender el valor por defecto y esa fijación única es la base de todo el nivel.
Llega un momento en toda aplicación en que un dato —el usuario autenticado, el tema, el idioma— tiene que estar disponible en decenas de componentes a distintas profundidades. Pasarlo de props en props es el prop drilling: ruidoso, frágil y acoplante. El contexto es la respuesta de Solid: un canal que atraviesa el árbol de owners y entrega un valor a cualquier descendiente que lo pida, sin que los intermediarios sepan que existe. createContext fabrica ese canal; el Provider decide qué viaja por él y para qué subárbol.
- Entender qué construye
createContext: un objeto conid,ProviderydefaultValue. - Proveer un valor a un subárbol con el
Providery ver que no añade nodo al DOM. - Comprender el valor por defecto: cuándo se devuelve y qué decisión de diseño comunica.
- Ver por qué el valor se fija una sola vez y qué implica eso para la reactividad.
El problema que resuelve: el prop drilling
Considera un Boton sepultado ocho niveles bajo la raíz que necesita conocer el tema activo. Sin contexto, el tema tendría que declararse como prop en Boton y en los siete ancestros que lo contienen, aunque a ninguno de ellos le incumba. Cada componente intermedio se convierte en una tubería que transporta un dato ajeno, y añadir un nuevo consumidor obliga a modificar toda la cadena. Es acoplamiento estructural puro: la forma del árbol dicta qué sabe cada pieza.
// Sin contexto: el tema cruza niveles que no lo usan, solo para llegar al Boton
<Barra tema={tema}>
<Menu tema={tema}>
<Boton tema={tema} /> {/* por fin alguien lo usa */}
</Menu>
</Barra>;
El contexto rompe ese acoplamiento. Un ancestro provee un valor y cualquier descendiente, a la profundidad que sea, lo consume directamente. Los intermediarios desaparecen de la ecuación: ni lo declaran, ni lo reenvían, ni se enteran. El árbol de owners —que ya conoces— es también el canal por el que viaja el contexto; aquí lo usamos a propósito.
createContext: el canal y su valor por defecto
createContext no crea estado ni reactividad: fabrica un identificador con una envoltura para proveerlo. Devuelve un objeto con tres campos.
import { createContext } from "solid-js";
const TemaContext = createContext<"claro" | "oscuro">("claro");
// ^ objeto con { id, Provider, defaultValue }
El id es un symbol único: la clave bajo la que el valor se guarda en el campo context de cada owner. Por ser un símbolo, dos contextos distintos jamás colisionan aunque tengan la misma forma. El Provider es el componente que inyecta un valor. Y defaultValue es lo que reciben los consumidores que no tienen ningún Provider por encima.
Ese valor por defecto es una decisión de diseño, no un relleno. Dos filosofías compiten. Una es dar un default con sentido —un tema claro, un logger que no hace nada, una configuración neutra— para que el componente funcione aislado, sin Provider, útil en pruebas y en catálogos de componentes. La otra es no dar default —lo veremos en las próximas lecciones— para que la ausencia de Provider sea un error detectable en vez de un silencio. Elegir uno u otro es declarar si tu contexto es opcional u obligatorio.
// Default con sentido: un logger que no hace nada.
// Sin Provider el componente funciona; con Provider, se inyecta el real.
const LogContext = createContext({ log: (_: string) => {} });
// Sin default: la ausencia de Provider sera un error, no un silencio.
const SesionContext = createContext<Sesion>(); // Sesion | undefined
El primer contexto es opcional: cualquiera puede consumirlo y, sin proveedor, recibe un logger inofensivo. El segundo es obligatorio: su tipo arrastra undefined para forzarte a decidir qué hacer cuando falta el proveedor —el tema de las dos próximas lecciones—.
Un error común es leer createContext(algo) como si algo fuese el estado de arranque que luego cambia. No lo es. El default solo se devuelve cuando falta el Provider; en cuanto hay uno, su value gana siempre, aunque el default fuese más específico. Piensa en el default como el comportamiento sin proveedor, no como el primer estado con proveedor.
Dos llamadas a createContext producen dos canales distintos aunque tengan el mismo tipo, porque cada una genera su propio symbol. Por eso el objeto que devuelve createContext debe crearse una vez y compartirse por import: si lo duplicas —o si la recarga en caliente lo recrea— tendrás dos id, y un consumidor podría leer un canal distinto del que otro proveyó, un fallo desconcertante que se manifiesta como un default inesperado. Declara cada contexto en su propio módulo y expórtalo desde ahí.
Provider: inyectar un valor en un subárbol
El Provider es un componente que recibe una prop value y envuelve a unos children. Todo lo que quede dentro —a cualquier profundidad— verá ese valor al consumir el contexto.
function App() {
return (
<TemaContext.Provider value="oscuro">
<Cabecera />
<Contenido />
</TemaContext.Provider>
);
}
Dos hechos definen su naturaleza. El primero: el Provider no genera ningún nodo en el DOM. No es un div envolvente; es un owner que fija una entrada en su campo context y renderiza sus hijos tal cual. No altera el layout ni el HTML resultante. El segundo: el alcance es exactamente el subárbol que envuelve. Fuera de él, el contexto vuelve al default o al Provider que corresponda más arriba.
flowchart TD CC[createContext con valor por defecto] --> PROV[Provider fija el valor del subarbol] PROV --> A[Cabecera] PROV --> B[Contenido] B --> HOJA[Boton consume el valor] HOJA -.trepa por owners.-> PROV FUERA[componente fuera del Provider] -.sin proveedor.-> CC style PROV fill:#89b4fa,color:#11111b style HOJA fill:#a6e3a1,color:#11111b style CC fill:#f9e2af,color:#11111b style FUERA fill:#f38ba8,color:#11111b
Como el valor se resuelve trepando por el árbol de owners, anidar dos providers del mismo contexto hace que el más interno gane para su subárbol: sobrescribe la entrada por su id. Es la base de patrones como un tema global con una sección que lo invierte, que veremos en la última lección.
La forma madura es un único módulo por contexto que exporta el Provider y el hook useX —lo veremos en la próxima lección— y mantiene el objeto de createContext privado. Quien lo use importa dos nombres, TemaProvider y useTema, y nunca toca el canal directamente. Esa cápsula es la unidad natural de un contexto: creación, provisión y consumo validado en un solo sitio.
El valor se fija una sola vez
Aquí está la sutileza que separa a Solid de React y que hay que interiorizar desde el primer día. Cuando el Provider se monta, lee props.value una vez, sin suscribirse, y lo guarda en el contexto del owner. No vuelve a observar esa prop. Si más tarde cambias lo que pasas a value, los consumidores no se enteran: en Solid no hay re-render que los vuelva a ejecutar.
// ESTO NO ES REACTIVO: al cambiar el signal, los consumidores no ven el cambio
const [tema, setTema] = createSignal("claro");
<TemaContext.Provider value={tema()}>...</TemaContext.Provider>;
// ^ se lee una vez y se congela ese string
// ESTO SI ES REACTIVO: provees el accesor, no su lectura
<TemaContext.Provider value={tema}>...</TemaContext.Provider>;
// ^ el consumidor llama a tema() y se suscribe el
La regla es nítida: para que un contexto sea reactivo, el valor que provees debe ser reactivo por dentro —un signal, un store, un objeto con accesores— y el consumidor obtiene la reactividad al leerlo, no al recibirlo. El contexto transporta la referencia; la reactividad vive en lo transportado. Las próximas lecciones construyen sobre esto: primero cómo consumir, luego cómo tipar, y después el patrón canónico de meter un store en el canal.
createContext
Fabrica el canal: un objeto con id (un símbolo), Provider y defaultValue. No crea estado.
Provider
Fija un value para su subárbol. No añade nodo al DOM. El más interno gana al anidar.
defaultValue
Lo que reciben los consumidores sin Provider. Decide si el contexto es opcional u obligatorio.
Fijación única
El value se lee una vez. Para reactividad, provee un signal o store, no su lectura.
La confusión de origen —casi siempre importada de React— es pensar que createContext crea un valor que vive y cambia. No crea nada de eso: crea una dirección, un symbol que sirve de clave, y un Provider que escribe bajo esa clave en el owner donde se monta. Consumir es leer trepando por el árbol de owners hasta la primera escritura, o caer al default si no hay ninguna. Entendido así, tres cosas encajan de golpe. Primero, por qué el Provider no pinta nada en el DOM: no es una etiqueta, es una anotación en un ámbito. Segundo, por qué el default no es un estado inicial sino el comportamiento cuando el canal está vacío: describe el sin-proveedor, no el primer valor. Y tercero, la más importante, por qué el valor se fija una sola vez: el contexto es fontanería, no reactividad, y por su tubería puedes hacer pasar una referencia inmutable —congelada para siempre— o una fuente reactiva —un signal, un store— que el consumidor leerá para suscribirse. Solid separa así dos responsabilidades que React funde en una: dónde está disponible un valor —el contexto— y cuándo cambia —la reactividad—. Dominar el nivel entero es tener siempre presente esa frontera: el Provider decide el alcance en el espacio del árbol; el signal que metes dentro decide la vida en el tiempo.
- Crea un
TemaContextconcreateContext("claro")y envuelve media aplicación en unProviderconvalue="oscuro"; comprueba en las DevTools que elProviderno añade ningún elemento al DOM. - Coloca un componente fuera del
Providery confirma que, al consumir, recibe el default"claro". - Anida un segundo
Providerconvalue="claro"dentro del de"oscuro"y verifica que su subárbol ve"claro". - Pasa
value={tema()}con un signal y cambia el signal: observa que los consumidores no reaccionan. Cambia avalue={tema}y explica por qué ahora sí podrían hacerlo.