Saltar al contenido

createReaction: observación manual

createReaction separa el rastreo de la reacción: llamas a track con una lectura para elegir qué observar, y tu callback se dispara una sola vez cuando cualquiera de esas fuentes cambia, sin re-suscribirse. Cómo re-armar la observación, en qué se diferencia de createEffect y de on con defer, y sus casos de uso avanzados para reacciones de un solo disparo.

Rastrear y reaccionar, en dos piezas separadas

createReaction recibe el callback de reacción y te devuelve una función track. La reacción no corre al crearla: primero eliges qué observar llamando a track con una función que lee signals; a partir de ahí, el callback se disparará la primera vez que cualquiera de esas fuentes cambie.

TSX
import { createSignal, createReaction } from "solid-js";const [dato, setDato] = createSignal("inicial");const track = createReaction(() => {  console.log("¡algo que observé cambió!");});track(() => dato()); // ahora observa a datosetDato("nuevo");    // dispara el callback UNA vezsetDato("otro");     // ya no dispara: la reacción se consumió

Fíjate en la asimetría con createEffect. En un efecto, el mismo cuerpo lee y reacciona, y se re-ejecuta cada vez, indefinidamente. En createReaction hay dos funciones con papeles distintos: track(fn) establece las dependencias ejecutando fn en un scope de rastreo —pero sin correr tu reacción—, y el callback que pasaste a createReaction es lo que corre cuando esas dependencias se invalidan, exactamente una vez.

Ese “una vez” es la esencia. Tras dispararse, la reacción queda inerte. Los cambios siguientes de dato no hacen nada, porque la suscripción se consumió al notificar. Es una alarma de un disparo, no un sensor continuo.

El caso más limpio de ese comportamiento es marcar un formulario como sucio en la primera edición, y solo en la primera:

TSX
const [sucio, setSucio] = createSignal(false);const track = createReaction(() => setSucio(true));track(() => campos()); // a la primera tecla, sucio pasa a true y deja de escuchar

En cuanto campos cambie una vez, sucio queda en true y la reacción se apaga sola: seguir observando no aportaría nada, porque ya está sucio para siempre. Con un createEffect tendrías que añadir una guarda —if (sucio()) return— que apague el vigilante a mano; createReaction expresa ese apagado en su propia semántica, sin ruido.

Re-armar: de un disparo a los que quieras

Si necesitas observar más de una vez, re-armas la reacción llamando a track otra vez —lo natural es hacerlo desde dentro del propio callback—. Así controlas con exactitud cuántas veces y bajo qué condiciones sigues escuchando.

TSX
const track = createReaction(() => {  console.log("cambió; me re-armo para la próxima");  track(() => dato()); // vuelve a observar});track(() => dato());

Esto reconstruye a mano lo que createEffect hace solo. Y ahí está la enseñanza: createReaction te entrega el mecanismo desnudo para que decidas tú la política de re-suscripción, en lugar de heredar la política automática. Puedes re-armar siempre, re-armar bajo condición, o cambiar qué observas en cada vuelta pasando una lectura distinta a track.

Cuándo es la herramienta correcta

createReaction es de nicho por diseño. La inmensa mayoría del código quiere createEffect o on. Reserva createReaction para cuando la semántica de un solo disparo es exactamente lo que el problema pide.

Construir una primitiva encima

El valor real de createReaction aflora al usarla como cimiento. Como te entrega el rastreo y la reacción por separado, puedes empaquetar políticas de re-suscripción a medida dentro de primitivas reutilizables. Como ejemplo, un alCambiar que ejecuta un callback en cada cambio de una fuente pero nunca en el montaje, re-armándose solo.

TSX
import { createReaction } from "solid-js";function alCambiar<T>(fuente: () => T, efecto: (v: T) => void) {  const track = createReaction(() => {    efecto(fuente());        // reacciona    track(() => fuente());   // se re-arma para el próximo cambio  });  track(() => fuente());     // primera observación, sin ejecutar el efecto}// uso: reacciona a cada cambio de estado, jamás al valor inicialalCambiar(estado, (v) => console.log("nuevo estado:", v));

Fíjate en lo que has construido: un on(fuente, efecto, { defer: true }) casero, ensamblado desde sus dos mitades. Podrías variar la política sin tocar el resto —disparar solo las tres primeras veces, esperar a que la fuente cumpla una condición, cambiar de fuente sobre la marcha— con retoques mínimos en el re-armado. Ese es el sentido de una primitiva de bajo nivel: no compite con on ni con createEffect, es la materia con la que se fabrican primitivas como esas. Cuando entiendes que las abstracciones cómodas del día a día se construyen con este ladrillo, dejas de verlo como una curiosidad y empiezas a verlo como el fondo del arsenal.