Cuándo NO usar contexto
El contexto de Solid es excelente para estado de subárbol y para inyección de dependencias, pero no es la respuesta por defecto al estado global. Para algo verdaderamente único en toda la app —un singleton de módulo— el contexto añade un proveedor, un consumo y una atadura al árbol sin comprar nada a cambio. Dos preguntas deciden: cuántas instancias pueden coexistir y si el estado depende de su posición en el árbol.
Existe una tentación, heredada de otros ecosistemas, de tratar el contexto como el hogar natural de todo estado compartido. En Solid esa costumbre suele ser un rodeo. Cuando el estado es genuinamente uno solo para toda la aplicación —un tema, una sesión, una caché de cliente—, un singleton importado directamente es más simple, más directo y más fácil de mover que cualquier proveedor. El contexto brilla en otro terreno: estado que pertenece a un subárbol, o que puede tener varias instancias legítimas a la vez. Esta lección traza esa frontera con dos preguntas que casi siempre bastan para no equivocarse.
- Formular las dos preguntas que deciden entre un singleton de módulo y un contexto.
- Reconocer el coste real del contexto: proveedor, consumo y dependencia del árbol.
- Elegir el singleton cuando el estado es único y no depende de su posición.
- Reservar el contexto para estado de subárbol, múltiples instancias o aislamiento por petición.
Dos preguntas que deciden
Casi todo el arte de esta decisión cabe en dos preguntas. La primera: ¿cuántas instancias de este estado pueden coexistir legítimamente? Si la respuesta honesta es «una y solo una para toda la app cliente» —hay un único tema, un único usuario conectado, una única caché—, entonces un singleton es la representación fiel de esa unicidad, y el contexto solo simula con maquinaria una restricción que el módulo te da gratis. Si la respuesta es «varias» —cada pestaña de un editor tiene su propio estado, cada formulario su propio borrador, cada widget su propia configuración—, el contexto es correcto, porque cada proveedor crea una instancia distinta para su subárbol.
La segunda: ¿el valor depende de dónde se consume en el árbol? El contexto existe precisamente para que un componente reciba un valor distinto según qué proveedor tenga encima —un tema anidado, una configuración regional que cambia por sección—. Si tu estado no depende de la posición —vale lo mismo lo consuma quien lo consuma—, esa capacidad del contexto está de más, y pagas su coste sin usar su poder.
flowchart TD Q1[cuantas instancias pueden coexistir] -->|una sola| S[singleton de modulo] Q1 -->|varias| Q2[depende de la posicion en el arbol] Q2 -->|si| C[contexto con proveedor] Q2 -->|no pero varias| C S --> R[import directo sin proveedor] style S fill:#a6e3a1,color:#11111b style C fill:#89b4fa,color:#11111b style Q1 fill:#f9e2af,color:#11111b
Fíjate en la asimetría: basta que una de las dos preguntas apunte al contexto para que el contexto gane. Si puede haber varias instancias, o si el valor depende de la posición, necesitas contexto aunque la otra respuesta fuera hacia el singleton. Solo cuando ambas apuntan al módulo —una sola instancia y sin dependencia de posición— el singleton es la elección correcta. Es un filtro conservador a favor del contexto, y aun así el estado verdaderamente global lo cruza sin esfuerzo.
Un triaje rápido con casos reales fija la intuición. El tema de la aplicación: una instancia, no depende de la posición —singleton—. El usuario conectado en una app de cliente: una instancia por pestaña, igual en todo el árbol —singleton—. El estado de un acordeón que puede anidarse: varias instancias, y cada nivel ve la suya —contexto—. La configuración regional que una sección puede sobreescribir para su subárbol: depende de la posición —contexto—. En ninguno de los cuatro hace falta deliberar mucho; las dos preguntas devuelven el veredicto casi solas, y esa es justo su virtud: convertir una decisión que la gente agoniza en un reflejo de dos pasos.
El coste real del contexto
Conviene ser concreto sobre qué cuesta el contexto, porque no es gratis y su precio se paga en cada uso. Un contexto exige, como mínimo, tres piezas: un createContext, un componente proveedor que envuelva la parte del árbol que debe verlo, y una llamada a useContext en cada consumidor. Y trae una atadura estructural: un componente solo puede consumir el contexto si está montado por debajo de su proveedor. Fuera de ese subárbol, useContext devuelve el valor por defecto o undefined.
// Contexto: proveedor obligatorio, consumo atado al arbol
const TemaCtx = createContext<TemaStore>();
function App() {
const tema = createTemaStore();
return (
<TemaCtx.Provider value={tema}>
<Pagina />
</TemaCtx.Provider>
);
}
function BotonTema() {
const tema = useContext(TemaCtx); // solo funciona bajo el proveedor
if (!tema) throw new Error("BotonTema fuera de TemaCtx.Provider");
return <button onClick={tema.alternar}>Tema</button>;
}
Compara con el singleton, que no tiene nada de eso: ni proveedor, ni envoltura, ni comprobación de que estás en el sitio correcto del árbol.
// Singleton: import y usa, desde cualquier parte
import { tema } from "../store/tema";
function BotonTema() {
return <button onClick={tema.alternar}>Tema</button>;
}
El singleton se consume desde cualquier punto —un componente, un manejador de eventos fuera del árbol, una función utilitaria, otro módulo— sin preguntar si hay un proveedor encima. No hay undefined posible, no hay guardas que escribir, no hay un árbol que respetar. Para estado que de verdad es uno solo, esa libertad es exactamente lo que quieres, y el proveedor solo sería una jaula decorativa.
El coste no acaba en el código de producción. Cada consumidor de contexto arrastra su exigencia a los tests: para probar un componente que llama a useContext, tienes que montarlo bajo un proveedor de prueba, o el contexto devuelve su valor por defecto y el test corre contra un estado que no es el real. Un componente que importa un singleton, en cambio, se prueba tal cual —o sustituyes el módulo con un doble si quieres aislarlo—. La atadura al árbol que en producción restringe el refactor, en los tests se cobra en andamiaje repetido alrededor de cada caso.
El modo en que el contexto falla es parte de su coste. Si un consumidor queda fuera de su proveedor, useContext no lanza un error: devuelve el valor por defecto que diste a createContext, o undefined si no diste ninguno. El fallo es silencioso y se manifiesta lejos, como un estado que misteriosamente no se comparte. Por eso el patrón robusto tipa el contexto como opcional y comprueba el resultado —lanzando un error explícito si es undefined—, un cinturón de seguridad que el singleton no necesita porque en él no existe el estado «fuera del proveedor».
Refactorizar delata la diferencia. Sacar un botón a otra parte del árbol, o llamar a una acción desde un manejador que vive fuera de todo componente, es trivial con un singleton —importas y ya— y frágil con contexto, porque cualquier movimiento que saque al consumidor de debajo del proveedor lo rompe en silencio. Si tu estado no necesita la localización que el contexto ofrece, esa localización se convierte en una restricción que estorba en cada refactor.
El error inverso: un módulo donde hacía falta contexto
La frontera tiene dos lados, y equivocarse en el sentido contrario es igual de común: encerrar en un singleton de módulo un estado que en realidad admite varias instancias. El síntoma es delator —dos partes de la interfaz que deberían ser independientes empiezan a pisarse— y su causa es haber respondido «una» a la primera pregunta cuando la respuesta honesta era «varias».
// ERROR: el estado de un editor como singleton de modulo
import { createStore } from "solid-js/store";
const [editor, setEditor] = createStore({ texto: "", cursor: 0 });
export { editor, setEditor };
Mientras haya un solo editor abierto, el singleton parece correcto. En cuanto abres una segunda pestaña de edición, ambas leen y escriben el mismo editor: teclear en una mueve el cursor de la otra, porque comparten la única instancia que el módulo permite. No hay forma de arreglarlo dentro del patrón singleton; el defecto es estructural, no un descuido que se parchee. La cura es darle a cada pestaña su propia instancia con la factory de la lección anterior, provista por contexto a cada subárbol de editor.
// CORRECTO: una instancia por pestana, servida por contexto
const EditorCtx = createContext<EditorStore>();
function PestanaEditor(props: { children: JSX.Element }) {
const editor = createEditorStore(); // instancia propia de esta pestana
return <EditorCtx.Provider value={editor}>{props.children}</EditorCtx.Provider>;
}
Aquí la primera pregunta —cuántas instancias— apuntaba a «varias» desde el principio, y el contexto es la única herramienta que las materializa: cada PestanaEditor monta su proveedor, cada proveedor crea su store, y las pestañas dejan de pisarse porque ya no comparten estado. Los dos errores son espejo uno del otro. Envolver en contexto un estado único paga una indirección que nunca se usa; encerrar en un singleton un estado múltiple niega una pluralidad que el dominio exige. Las dos preguntas de la primera sección atrapan ambos fallos por igual, y ese es su valor: no son un truco para justificar el singleton, sino un filtro que apunta con la misma precisión hacia el módulo cuando toca y hacia el contexto cuando toca. Hacérselas antes de escribir la primera línea del store cuesta un minuto; descubrir la respuesta por el dolor cuesta un refactor.
Dónde gana cada uno
La conclusión no es que el contexto sobre —es una herramienta excelente— sino que resuelve un problema distinto del que la gente le suele echar encima. El contexto es, en el fondo, inyección de dependencias por el árbol: sirve un valor a un subárbol sin pasarlo por props, y permite que distintas ramas reciban valores distintos. Ese es su dominio, y ahí no tiene rival.
El caso donde es insustituible lo deja nítido: un sistema de temas anidables, en el que una sección de la página declara su propio tema y todo lo que cuelga de ella lo hereda, mientras el resto sigue con el global. Ahí el valor depende de la posición por diseño, y ninguna variable de módulo puede expresar «el tema vigente aquí», porque no existe un «aquí» único —hay tantos como puntos del árbol—. El contexto está construido exactamente para esa pregunta: qué valor rige en este punto concreto de la jerarquía. Responderla es su razón de ser, no un efecto colateral, y es la clase de cosa que un singleton no puede ni empezar a modelar.
Singleton de módulo
Estado único de toda la app: tema, sesión, caché de cliente, preferencias. Una instancia, sin dependencia de posición. Import directo, cero proveedor.
Contexto
Estado de subárbol o inyección de dependencias: varias instancias legítimas, valor que cambia por rama, o aislamiento por petición en SSR. Proveedor y useContext.
El caso donde el contexto pasa de recomendable a obligatorio merece adelantarse, porque es el que cierra el nivel: el renderizado en servidor. En SSR un módulo se comparte entre todas las peticiones del proceso, así que un singleton de módulo que guarde estado por usuario los mezcla entre peticiones. Ahí el contexto deja de ser una cuestión de gusto: proveer el estado en la raíz del árbol —que se re-ejecuta por petición— es la única forma de que cada usuario tenga el suyo. Pero fíjate en el matiz: eso no contradice esta lección, la confirma. En SSR el estado por usuario puede tener varias instancias coexistiendo —una por petición en vuelo— y por tanto la primera pregunta ya apuntaba al contexto. El servidor no inventa una regla nueva; hace visible, por fuerza, la que ya regía.
El malentendido que origina casi todo el mal uso del contexto es tratarlo como sinónimo de estado global, cuando son ideas ortogonales que a veces se solapan. Estado global es una pregunta sobre alcance: quién puede ver este valor. Contexto es una respuesta a una pregunta distinta, sobre procedencia: de qué proveedor del árbol viene el valor que veo aquí. Que a menudo uses contexto para compartir estado no significa que el contexto sea el mecanismo del estado global; significa que la inyección por el árbol es una forma de distribuir estado, útil cuando la procedencia importa. Cuando no importa —cuando el valor es el mismo lo consuma quien lo consuma— usar contexto es responder una pregunta que nadie hizo, y pagas el precio de una indirección sin comprar su beneficio. La prueba decisiva es imaginar dos consumidores en ramas distintas del árbol: si deben ver valores potencialmente distintos, el contexto está ganándose su sueldo; si por diseño verán siempre el mismo valor, el proveedor es andamiaje que envuelve un singleton disfrazado. En Solid esta distinción es más nítida que en un modelo de re-render, porque aquí el estado global no arrastra el impuesto de propagación que en otros frameworks empuja a la gente hacia el contexto por rendimiento. Un signal de módulo actualiza solo los nodos exactos que lo leen, esté donde esté el consumidor. Liberado de ese impuesto, puedes elegir el contexto por la razón correcta —la procedencia importa, hay varias instancias— y no por miedo a un coste que en Solid no existe. La madurez, aquí, es resistir la costumbre de envolverlo todo en proveedores y reservar el contexto para cuando de verdad inyectas una dependencia, no para cuando simplemente compartes un valor.
- Para un tema claro/oscuro único de toda la app, implementa la versión singleton y la versión contexto; enumera qué código extra pide la de contexto sin dar nada a cambio.
- Para un editor con varias pestañas, cada una con su propio estado de edición, argumenta por qué el contexto es la elección correcta y el singleton imposible.
- Aplica las dos preguntas —cuántas instancias, depende de la posición— a tres estados de tu elección y justifica el veredicto de cada uno.
- Demuestra la fragilidad del contexto moviendo un consumidor fuera de su proveedor y observando el fallo; repite el movimiento con el singleton y comprueba que sigue funcionando.
- Explica por qué el SSR convierte al contexto en obligatorio para el estado por usuario sin contradecir la regla de esta lección.