Closures y captura de valores
Por qué leer un signal y guardarlo en una const lo congela para siempre, y por qué en Solid siempre se lee por función. El corazón del modelo de ejecución y la fuente de casi todos los bugs de reactividad.
Congelar frente a leer tarde
El bug y su arreglo, uno al lado del otro:
function Roto() { const [n, setN] = createSignal(0); const valor = n(); // captura 0 AHORA, para siempre return ( <button onClick={() => setN(n() + 1)}> {valor} </button> );}function Bien() { const [n, setN] = createSignal(0); return ( <button onClick={() => setN(n() + 1)}> {n()} </button> );}const valor = n() ejecuta n() una vez, durante la única corrida del cuerpo, y guarda el primitivo 0. A partir de ahí valor ya no tiene ninguna conexión con el signal: es un número muerto. {n()} en el JSX, en cambio, es una llamada que ocurre dentro del efecto que generó el compilador, y se vuelve a ejecutar cada vez que n cambia. Misma sintaxis de lectura, destinos opuestos: uno se ejecuta en el plano, el otro dentro del grafo.
La regla de oro: pasa la función, no el valor
Los paréntesis son el acto de “leer ahora”. Retrásalos. Cuando quieras compartir reactividad, pasa la fuente (n), no la lectura (n()), o léela justo en el punto de uso.
// pasar la fuente: el consumidor lee cuando quiera, sigue vivo<Hijo valor={n} />// dentro del hijo: props.valor() -> lectura reactivaAquí hay un matiz que conviene precisar para 2026. En el JSX y en las props, el compilador ya envuelve tus expresiones en getters, así que valor={n()} también sigue vivo: props.valor re-evalúa la expresión n() cada vez que se accede. El pecado no es la sintaxis de la prop, sino sacar la lectura a una const del cuerpo o destructurar las props, porque eso ejecuta la lectura una vez y guarda un valor muerto.
Dónde muerde: props, efectos y manejadores
El mismo principio explica tres situaciones que parecen distintas:
- Props: nunca destructures. Lee
props.valoren el punto de uso; usasplitPropspara separar lo local de lo que reenvías. - Efectos: lee el signal dentro del
createEffectpara que el efecto se suscriba. Si lo lees fuera y pasas el valor, el efecto no rastreará nada. - Manejadores: leer dentro del handler (
onClick={() => setN(n() + 1)}) toma el valor vigente en el momento del clic, que es justo lo que quieres.
import { splitProps } from "solid-js";function Boton(props) { // NO: const { variante, ...resto } = props -> congela y pierde reactividad const [local, resto] = splitProps(props, ["variante"]); return <button class={local.variante} {...resto} />;}Un corolario práctico: en un manejador de evento, lee el signal dentro del manejador, no lo captures al declararlo. Así siempre operas sobre el valor vigente en el momento del clic.
const [n, setN] = createSignal(0);// MAL: captura n al crear el handler, se queda con el valor inicialconst inc = ((v) => () => setN(v + 1))(n());// BIEN: lee n cuando se dispara el eventoconst inc2 = () => setN(n() + 1);Lo mismo vale dentro de un efecto: si lees el signal fuera y pasas el valor, el efecto no se suscribe; si lo lees dentro, sí. La lectura tardía no es preferencia de estilo, es la condición para que exista la arista en el grafo.
Este es el porqué último de toda la regla: el grafo no se declara, se teje leyendo, y solo se teje donde y cuando lees.
El caso de los stores: getters hasta el fondo
Con createStore la regla no cambia, solo se vuelve más sutil: el acceso a una propiedad anidada es en sí mismo una lectura rastreable. Por eso tampoco se destructura un store, y por eso pasar estado.usuario a un hijo y leer allí props.usuario.nombre mantiene la reactividad hasta la hoja del árbol.
import { createStore } from "solid-js/store";const [estado, setEstado] = createStore({ usuario: { nombre: "Ada" } });// MAL: captura el string ahora, se congelaconst nombre = estado.usuario.nombre;// BIEN: lee en el punto de uso, dentro del JSX o de un efecto<h1>{estado.usuario.nombre}</h1>La lección general es que en Solid casi nada es “un valor”: casi todo es “un acceso que, al ejecutarse, lee y se suscribe”. Un signal es una función; una propiedad de store es un getter; una prop es un getter. Congelar cualquiera de ellos en una const del cuerpo lo desconecta del grafo.
Es el mismo fenómeno que el clásico bug del bucle con var en JavaScript, trasladado a la reactividad: capturar demasiado pronto un valor que aún iba a cambiar. La cura también es la misma que allí se logró con let: mover la lectura al momento en que de verdad se necesita.