Skip to content

Signals & Effects

Lume uses a signal-based reactivity system. Signals hold values, and effects automatically re-run when their dependencies change.

A signal is a reactive value container:

const count = signal(0);

// Read the current value
count(); // → 0

// Set a new value
count.set(5);

// Update based on current value
count.update((v) => v + 1); // → 6
MethodDescription
signal()Read the current value
signal.set(value)Set a new value
signal.update(fn)Transform the current value

Effects are functions that automatically re-run when any signal they read changes:

const name = signal("World");

effect(() => {
  console.log(`Hello, ${name()}!`);
});
// Logs: "Hello, World!"

name.set("Lume");
// Logs: "Hello, Lume!"

Effects use synchronous tracking. When an effect runs:

  1. Lume records which signals are read (called)
  2. When any of those signals change, the effect re-runs
  3. Dependencies are re-computed on every run (dynamic tracking)

Effects can return a cleanup function that runs before each re-execution and on unmount:

effect(() => {
  const interval = setInterval(() => console.log(count()), 1000);
  
  return () => clearInterval(interval); // cleanup
});
const disclosure = defineComponent(({ part, signal, on, effect }) => {
  const button = part("button");
  const panel = part("panel");
  const open = signal(false);

  on(button, "click", () => open.update((v) => !v));

  effect(() => {
    // This effect re-runs whenever `open` changes
    panel.hidden = !open();
    button.setAttribute("aria-expanded", String(open()));
  });
});
  • Synchronous: Signal reads are synchronous and tracked automatically
  • Fine-grained: Only effects that depend on a changed signal re-run
  • No batching needed: Updates are immediate and predictable
  • Auto-cleanup: All effects are cleaned up when the component unmounts