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.
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.
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 arbolconst 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 parteimport { 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 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 moduloimport { 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 contextoconst 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.
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.