on: dependencias explícitas y defer
on invierte el rastreo automático: declaras a mano qué fuentes disparan la reacción y el resto del cuerpo corre sin suscribirse a nada. La forma on con una o varias deps, el callback que recibe valor actual, previo y retorno anterior, y la opción defer para saltar la ejecución inicial y reaccionar solo a cambios posteriores, como un watcher.
Declarar las dependencias en vez de descubrirlas
on no es una primitiva reactiva por sí misma: es una fábrica de cuerpos que entregas a createEffect, createMemo o createComputed. Recibe las dependencias y una función, y devuelve otra función que rastrea solo esas dependencias y ejecuta el cuerpo sin rastrear nada más.
import { createSignal, createEffect, on } from "solid-js";const [a, setA] = createSignal(0);const [b, setB] = createSignal(0);createEffect( on(a, (valorA) => { console.log("reacciona a A:", valorA, "y lee B:", b()); // b NO es dependencia }));Este efecto se dispara únicamente cuando cambia a. Dentro del cuerpo lees b() con total libertad y sin consecuencias: como el cuerpo corre fuera del rastreo, esa lectura no crea suscripción. Compáralo con el modelo por defecto, donde leer b() ahí dentro te ataría a b sin remedio. on te da un cuerpo hermético cuya única puerta de entrada es la lista de deps.
El callback recibe más de lo que parece. Su firma completa es (input, prevInput, prevResultado) => resultado: el valor actual de las deps, el valor que tenían en la ejecución anterior, y lo que el propio callback devolvió la última vez.
createEffect( on(a, (actual, previo, cuenta) => { console.log(`A pasó de ${previo} a ${actual}`); return (cuenta ?? 0) + 1; // acumulador: cuántas veces reaccionó }));Varias fuentes y su uso con memo
Cuando pasas un array de accesores, on rastrea todos, y el callback recibe arrays paralelos para el valor actual y el previo. Cualquiera de las fuentes que cambie dispara la reacción.
createEffect( on([a, b], ([na, nb], [pa, pb]) => { console.log("cambió A o B:", na, nb, "antes:", pa, pb); }));on brilla igual dentro de un createMemo, cuando quieres un valor derivado que se recalcule solo ante ciertas fuentes e ignore otras que también lee para computar.
import { createMemo } from "solid-js";// se recalcula solo cuando cambia filtro; lee orden sin depender de elconst vista = createMemo( on(filtro, () => aplicar(filtro(), items(), orden())));Aquí vista se recomputa cuando cambia filtro, tomando de paso los items y el orden vigentes sin suscribirse a ellos. Has elegido con precisión el único eje que gobierna la derivación.
Las deps no tienen por qué ser signals crudos: sirve cualquier accesor —un createMemo, una función derivada, o () => props.valor para escuchar una prop reactiva—. A on solo le importa recibir funciones que pueda leer en su scope de rastreo; de dónde salga el valor le es indiferente. Eso te deja fabricar disparadores compuestos: on(() => a() + b(), fn) trata la suma como una sola fuente, porque su cuerpo rastrea a y b, y dispara cuando cambie cualquiera de los dos. Envolver la dep en un createMemo con su propio equals te permite además cortar el disparo cuando el valor derivado no cambia de verdad.
defer: no reacciones al montar, solo a los cambios
Por defecto, un efecto corre una vez al crearse. A veces eso no es lo que quieres: buscas un watcher puro, que se quede callado en el montaje y solo hable cuando la fuente cambie de verdad más adelante. Para eso está la opción defer.
createEffect( on( seleccion, (id) => guardarPreferencia(id), // NO corre al montar { defer: true } // solo cuando seleccion cambie después ));Con defer: true, la primera ejecución se salta por completo. El cuerpo no corre hasta que seleccion cambia por primera vez, y en esa primera reacción prevInput será undefined, porque no hubo run previo que registrara un valor. Es el equivalente exacto del watch con immediate: false de otros frameworks: reaccionar al cambio, no al estado inicial.
El precio: la lista que tú mantienes
Al declarar las deps a mano recuperas un riesgo que el rastreo automático había abolido: la dependencia olvidada. Si el cuerpo empieza a usar una fuente nueva como disparador pero no la añades a la lista, el efecto no reaccionará a ella, y no habrá error —solo una reacción que se queda corta—.
// BUG sutil: el cuerpo usa `orden`, pero solo `filtro` está en las depscreateEffect( on(filtro, () => { render(items(), filtro(), orden()); // cambiar `orden` NO redibuja }));Cambiar orden no dispara nada, porque on solo escucha a filtro. Puede que sea exactamente lo que buscabas —orden como contexto inerte— o puede ser un despiste. La diferencia entre intención y bug no vive en el código, sino en tu cabeza, y on no puede adivinarla. Esta es la carga que aceptas al invertir el modelo: las dependencias pasan a ser tu responsabilidad, igual que las listas de useEffect en React que Solid presumía de haber jubilado. La conclusión práctica no es evitar on, sino usarlo cuando esa carga compra algo concreto —precisión sobre el disparo, o defer— y nunca por simple desconfianza del automático.