Múltiples providers, composición y rendimiento
Un contexto por preocupación, providers compuestos sin caer en la pirámide de anidamiento, y el hecho que lo cambia todo respecto a React: en Solid el contexto no provoca re-render, porque no hay render. La lectura es una búsqueda única por owners y la reactividad vive solo en los signals y stores que metes dentro. De ahí se deduce cuándo conviene elevar un Provider y cuándo acotarlo.
Una aplicación real no tiene un contexto, tiene varios: sesión, tema, idioma, carrito, cada uno con su ciclo de vida. Gestionarlos bien es saber tres cosas: separarlos por preocupación, componer sus providers sin ahogar el árbol en anidamiento, y —lo que de verdad distingue a Solid— entender que el contexto no cuesta renders, porque en Solid no hay renders. Ese último hecho invierte por completo los consejos de rendimiento que traes de React y decide, en última instancia, dónde colocar cada Provider.
- Separar el estado en un contexto por preocupación y componer sus providers.
- Aplanar la pirámide de providers con un componente agregador o un combinador.
- Comprender por qué en Solid el contexto no provoca re-render y qué implica.
- Decidir a qué altura del árbol elevar cada
Providersegún sus consumidores.
Un contexto por preocupación
La primera regla es de diseño, no de rendimiento: un contexto por eje de estado independiente. La sesión no tiene que ver con el tema, ni el tema con el carrito; mezclarlos en un solo contexto acopla lo que cambia por razones distintas y a ritmos distintos. Cada preocupación crea su propio createContext, su Provider y su hook useX, y se compone con los demás por anidamiento.
function App() {
return (
<SesionProvider>
<TemaProvider>
<CarritoProvider>
<Rutas />
</CarritoProvider>
</TemaProvider>
</SesionProvider>
);
}
Cada Provider envuelve al siguiente, y Rutas —con todo lo que cuelga de ella— consume cualquiera de los tres desde la profundidad que sea. El orden de anidamiento solo importa si un provider necesita consumir a otro: el interno puede leer al externo, nunca al revés.
El componente que renderiza un Provider queda por encima de él en el árbol, no por debajo, así que un useContext en su cuerpo no ve el valor que ese mismo componente provee: trepa por owners y pasa de largo. Solo los descendientes del Provider —los children— lo consumen. Si necesitas el valor en el propio componente que lo crea, ya lo tienes en una variable local; el contexto es para repartirlo hacia abajo.
Componer sin la pirámide
Con cuatro o cinco contextos, ese anidamiento degenera en una escalera incómoda —la pirámide de providers—. Se aplana de dos formas. La sencilla: un componente Providers que encapsula la escalera y se usa una vez en la raíz.
const Providers: ParentComponent = (props) => (
<SesionProvider>
<TemaProvider>
<CarritoProvider>{props.children}</CarritoProvider>
</TemaProvider>
</SesionProvider>
);
La genérica, cuando la lista crece o se arma dinámicamente: un combinador que pliega un array de providers en uno solo.
import { type ParentComponent, type JSX } from "solid-js";
function combinar(...proveedores: ParentComponent[]): ParentComponent {
return (props) =>
proveedores.reduceRight(
(hijos, Prov) => <Prov>{hijos}</Prov>,
(<>{props.children}</>) as JSX.Element,
);
}
const Providers = combinar(SesionProvider, TemaProvider, CarritoProvider);
El reduceRight respeta el orden: el primero de la lista queda más externo. Un combinador así convierte la composición de contextos en datos —una lista— en vez de estructura anidada a mano.
combinar compone providers cuya única entrada son sus children. Si un Provider necesita configuración —<TemaProvider inicial="oscuro">—, o lo dejas fuera del pliegue y lo anidas a mano, o le pasas la configuración por otra vía. Para el caso común de providers autosuficientes, el combinador es perfecto; para los parametrizados, la escalera explícita sigue siendo lo más claro.
El contexto no provoca re-render
Este es el corazón del nivel y el punto donde Solid rompe con React de forma más tajante. En React, cambiar el value de un Provider re-renderiza todos los consumidores de ese contexto, aunque solo les interese una parte; de ahí nace toda una industria de mitigaciones: trocear contextos, memoizar el value, usar selectores. En Solid, nada de eso existe, porque nada se re-renderiza.
La lectura con useContext es una búsqueda única por el árbol de owners que ocurre una vez, cuando el componente se crea. Devuelve una referencia y ahí acaba su papel. La reactividad no la aporta el contexto: la aportan los signals y stores que viajan dentro. Un consumidor se actualiza —de forma quirúrgica— solo cuando cambia la hoja reactiva concreta que leyó, nunca porque “el contexto cambió”.
flowchart TD P[Provider fija la tupla una vez] --> C1[Consumidor A lee tema] P --> C2[Consumidor B lee idioma] S[cambia solo el tema] -.grano fino.-> C1 S -.no lo toca.-> C2 style P fill:#89b4fa,color:#11111b style C1 fill:#a6e3a1,color:#11111b style C2 fill:#45475a,color:#cdd6f4
La consecuencia práctica es liberadora: puedes permitirte un contexto “gordo”. Meter un store rico con veinte campos en un solo contexto no penaliza a nadie, porque el grano fino del store notifica campo por campo. El consejo de React de partir contextos para evitar renders no solo es innecesario en Solid: es contraproducente, porque fragmenta sin motivo lo que conceptualmente va junto. En Solid, la granularidad la da el grafo reactivo, no la frontera del contexto.
Esto no te autoriza a meterlo todo en un canal: la regla de una preocupación por contexto sigue siendo buena arquitectura. Lo que cambia es el motivo por el que separas. Ya no divides por miedo al rendimiento —ese impuesto no existe—, sino por cohesión y por ciclo de vida: la sesión y el carrito nacen y mueren en momentos distintos, y mezclarlos en un contexto los ataría sin razón. Un contexto por eje conceptual, cada uno tan gordo como ese eje pida.
Como cada Provider fija su valor por id, anidar dos del mismo contexto hace que el interno gane para su subárbol. Es el mecanismo de las excepciones locales: un tema global oscuro con una sección concreta forzada a claro, un idioma por defecto con un widget en otro. No necesitas un contexto nuevo para una excepción; provees otra vez, más abajo, y ese subárbol ve el valor nuevo mientras el resto sigue con el de arriba.
Cuándo elevar el Provider
Que el contexto no cueste renders no significa subir todo a la raíz. La altura correcta de un Provider es el ancestro común más bajo de sus consumidores. Tres casos cubren casi todo.
Global: cerca de la raíz
Sesión, tema, idioma —lo que toda la app consulta— va arriba del todo. Una instancia para todos.
De subárbol: sobre la región
Un asistente, un panel, un formulario complejo: el Provider justo encima, y su estado muere con la región.
Por instancia: uno por unidad
Cada acordeón, cada fila editable: un Provider por instancia da estados aislados que no se pisan.
El caso “por instancia” es el más revelador, porque el mismo Provider montado varias veces da estados aislados sin una línea extra: la posición en el árbol es la identidad del estado.
function Panel() {
return (
<>
{/* dos acordeones, cada uno con su propio estado abierto/cerrado */}
<AcordeonProvider><Seccion titulo="Envío" /></AcordeonProvider>
<AcordeonProvider><Seccion titulo="Pago" /></AcordeonProvider>
</>
);
}
El error en un sentido es elevar de más: subir a la raíz un estado que solo usa una pantalla lo mantiene vivo toda la sesión y lo hace único cuando quizá querías uno por instancia. El error opuesto es acotar de menos: si dos primos necesitan el mismo estado y pones el Provider en uno de ellos, el otro no lo alcanza. La brújula es siempre la misma: localiza a todos los consumidores, encuentra su ancestro común más bajo, y pon ahí el Provider. Ni un nivel más arriba —o pierdes aislamiento y limpieza—, ni uno más abajo —o dejas consumidores fuera de alcance—. Que valga la pena usar contexto para ese estado fue la decisión del nivel anterior; este resuelve cómo proveerlo bien una vez decidido que sí.
Todo el instinto que traes de React sobre el contexto hay que recablearlo, y el cable maestro es este: el Provider de Solid no es un punto de re-render, es una anotación de alcance en el árbol de owners. Cambiar lo que provees no despierta a nadie; leerlo no suscribe a nada; la única reactividad que existe es la de los signals y stores que hiciste viajar por dentro, y esa reactividad es de grano fino, hoja por hoja, ajena por completo a la frontera del contexto. De aquí se deducen, sin memorizar reglas, todas las decisiones del nivel. ¿Un contexto gordo o varios finos? Gordo si conceptualmente va junto, porque partir no ahorra renders que no ocurren; la fragmentación de contextos de React es una optimización para un problema que Solid no tiene. ¿Dónde pongo el Provider? En el ancestro común más bajo de sus consumidores, ni más arriba —o sacrificas aislamiento, ciclo de vida y la posibilidad de instancias múltiples— ni más abajo —o dejas huérfano a algún consumidor—. ¿Cómo hago una excepción local? Anidas otro Provider del mismo contexto y su id gana para ese subárbol, sin inventar un canal nuevo. ¿Cómo compongo cinco contextos sin una pirámide? Los pliegas con un combinador, porque son datos, no estructura sagrada. Cuando de verdad interiorizas que el contexto es dónde y el grafo reactivo es cuánto y cuándo, dejas de razonar en términos de renders y de consumidores que se repintan, y empiezas a razonar en términos de alcance y de hojas reactivas —que es como razona Solid por dentro—. Ese cambio de marco es el destilado de todo el nivel: proveer es delimitar un ámbito; reaccionar es cosa del grafo; y separar limpiamente ambas preguntas es lo que te deja construir aplicaciones grandes llenas de contextos sin el menor miedo al rendimiento.
- Crea tres contextos —sesión, tema, carrito— con sus hooks y compón sus providers primero anidándolos y luego con un componente
Providersagregador. - Escribe el combinador
combinarconreduceRighty reconstruyeProvidersa partir de una lista; confirma que el orden externo-interno se respeta. - Mete un store con varios campos en un solo contexto y comprueba, con logs, que un consumidor que lee un campo no se actualiza cuando cambia otro: el contexto gordo no penaliza.
- Anida un segundo
TemaProvidercon valor distinto sobre una sección y verifica que solo ese subárbol cambia de tema. - Toma un estado que hoy está en la raíz pero solo usa una pantalla; bájalo a su ancestro común más bajo y observa que ahora se limpia al salir de esa pantalla.