wandres.dev
CREATEEFFECT · reacciones a los signals

Qué es un efecto y cuándo corre

createEffect crea una reacción: una computación que se re-ejecuta cuando cambian sus dependencias. Corre tras el render, en la cola de efectos, agrupada por lotes, y siempre con un primer run que lo arranca todo.

⏱ 13 min

Un signal guarda un valor; un memo lo deriva; un efecto reacciona. El efecto es la hoja del grafo reactivo: el punto donde la reactividad se asoma al mundo y produce un cambio observable —pinta, registra, sincroniza—. Donde React exige useEffect con su lista de dependencias escrita a mano, Solid descubre las dependencias solo y ejecuta en el instante exacto. Entender cuándo corre un efecto, y por qué, es entender el motor de reacciones sobre el que se apoya todo lo demás.

🎯 Al terminar esta lección sabrás
  • Definir un efecto como una computación que reacciona, no que deriva ni devuelve.
  • Situar el efecto en la cola de reacciones: tras el render y tras las computaciones puras.
  • Comprender el primer run, ese que todo efecto ejecuta una vez al nacer.
  • Ver el batching: muchas escrituras síncronas colapsan en una sola reacción.

Un efecto es una hoja del grafo

El grafo reactivo tiene tres clases de nodo. Los signals son fuentes: no dependen de nadie, solo emiten. Los memos son intermedios: dependen de otros nodos y a la vez pueden ser leídos. Los efectos son sumideros: dependen de otros nodos, pero nadie los lee, porque no devuelven un valor útil al grafo.

Por eso los efectos son hojas. Su trabajo no es producir un dato sino ejecutar un efecto secundario cada vez que su entrada cambia. Todo lo que en una interfaz “sale” del sistema reactivo —un log, una mutación del DOM, una petición— vive en un efecto.

createEffect recibe una función y la convierte en esa hoja. Al leer un signal dentro de ella, el efecto queda suscrito; cuando el signal cambie, el efecto volverá a correr.

import { createSignal, createEffect } from "solid-js";

function Contador() {
  const [n, setN] = createSignal(0);

  createEffect(() => {
    console.log("n vale", n());
  });

  return <button onClick={() => setN(n() + 1)}>{n()}</button>;
}

El cuerpo del componente corre una sola vez —Solid no re-renderiza—, así que createEffect no se registra en cada render como en React. Se registra una vez y a partir de ahí vive por su cuenta, reaccionando a n mientras exista.

ℹ️
Un efecto no devuelve nada al grafo

Si un memo es un nodo que otros pueden leer, un efecto es un callejón sin salida: su retorno no lo consume ningún otro nodo reactivo (lo veremos matizado en la lección del valor previo, donde el retorno se lo pasa a sí mismo). Cuando notes que un efecto existe solo para calcular algo que luego lees en otro sitio, esa es la señal de que querías un createMemo. Deriva con memos; reacciona con efectos.

El primer run: la chispa

Todo efecto corre al menos una vez. Ese primer run no es opcional: es el momento en que el efecto ejecuta su función por primera vez, lee sus signals y así descubre de quién depende. Sin él no habría suscripciones y el efecto nunca reaccionaría a nada.

La sutileza está en cuándo ocurre. En createEffect, el primer run se difiere hasta después del render inicial: Solid crea primero el DOM y solo entonces vacía la cola de efectos. Por eso dentro de un efecto el DOM ya existe y las refs ya apuntan a nodos reales.

function Perfil() {
  let caja: HTMLDivElement | undefined;

  createEffect(() => {
    // el primer run ocurre despues del render:
    // `caja` ya apunta al div real, nunca a undefined
    console.log(caja?.offsetHeight);
  });

  return <div ref={caja}>contenido</div>;
}

Es el análogo fino de un useEffect con dependencias vacías, pero sin lista que mantener y sin re-registro en cada render.

La cola de reacciones

En las ejecuciones siguientes, una escritura no dispara el efecto en el acto. Solid marca los observadores como obsoletos, recomputa primero las computaciones puras —memos y computeds— y solo al final vacía la cola de efectos.

El efecto corre así sobre un grafo ya consistente, nunca a medio actualizar. Ese orden —marcar, recomputar puros, vaciar efectos— es la ley que gobierna todo el sistema.

flowchart TD
W[setN escribe el signal] --> M[marca stale a sus observadores]
M --> U[fase pura recomputa memos y computeds]
U --> Q[cola de efectos]
Q --> R[el efecto re-ejecuta su cuerpo]
style W fill:#f9e2af,color:#11111b
style U fill:#89b4fa,color:#11111b
style R fill:#a6e3a1,color:#11111b

Batched: una reacción por lote

Solid no re-ejecuta un efecto por cada escritura, sino una vez por lote. Dentro de un manejador de eventos, todas las escrituras se agrupan automáticamente: aunque toques tres signals que el efecto observa, correrá una sola vez, al terminar el manejador.

const [x, setX] = createSignal(0);
const [y, setY] = createSignal(0);

createEffect(() => console.log("suma", x() + y()));

function mover() {
  setX(10);
  setY(20);
  // el efecto corre UNA vez con x=10, y=20; jamas ve el estado intermedio
}

Fuera de un manejador, si necesitas agrupar escrituras a mano, envuélvelas en batch. La garantía es la misma en ambos casos.

import { batch } from "solid-js";

batch(() => {
  setX(1);
  setY(2);
}); // una sola reaccion al cerrar el lote

Un efecto jamás observa un estado a medio construir: ve el antes o el después de un lote, nunca el durante. Esta propiedad —ausencia de estados intermedios visibles— es lo que en la literatura reactiva se llama ser glitch-free, y es lo que hace fiable razonar sobre un efecto: lo que lees dentro es siempre coherente entre sí.

💡
El primer run también sirve para inicializar

Como el efecto corre al nacer, puedes usarlo para sincronizar el mundo externo con el estado inicial: fijar el title del documento, arrancar una suscripción, pintar en un canvas. No necesitas un efecto para el arranque y otro para las actualizaciones; el mismo cuerpo cubre ambos, porque el primer run y los reruns ejecutan exactamente el mismo código.

El efecto es el borde del sistema reactivo

Conviene ver el grafo entero como una máquina que empuja y luego tira. Cuando escribes un signal, Solid empuja una marca de obsolescencia por las aristas: no recalcula nada todavía, solo ensucia a quien dependía. Después, en un orden topológico —primero las computaciones puras, luego los efectos—, tira de los valores recomputando solo lo ensuciado. Ese doble movimiento, push de invalidación y pull de recomputación, es lo que garantiza que cada nodo se evalúe una sola vez por lote y en el momento en que todas sus entradas ya son definitivas. Los efectos son deliberadamente los últimos de esa cadena porque son el borde: el lugar donde la reactividad deja de ser un cálculo interno y se convierte en un cambio real —un log, un nodo del DOM, una petición—. Situarlos al final no es un capricho de implementación: es lo que asegura que cuando tu efecto toca el mundo, el mundo interno de Solid ya está cerrado y es consistente. Y por eso mismo el primer run se difiere hasta después del render: un efecto que existe para hablar con el DOM no tendría sentido corriendo antes de que ese DOM exista. Interioriza esta cola —marcar, recomputar puros, vaciar efectos— y dejarás de preguntarte por qué un efecto corrió cuando corrió; lo sabrás por construcción.

⚔️ Observa el motor de reacciones
  1. Crea un efecto que registre un signal y comprueba en consola que corre una vez al montar, antes de tocar nada.
  2. Añade un ref a un elemento y léelo dentro del efecto; confirma que no es undefined, prueba de que el primer run va tras el render.
  3. Escribe dos signals que el efecto observe dentro de un mismo manejador de clic y verifica que el efecto corre una sola vez, con ambos valores nuevos.
  4. Convierte un efecto cuyo único trabajo era calcular un valor en un createMemo y razona por qué ese nodo ya no es una hoja.