Skip to content

Signals & Effects

Rivet ships a tiny, synchronous reactive system: signals hold values, effects react to changes, computed derives values. Dependencies are tracked automatically — no manual subscribing.

A signal is a container for a value. Reading happens by calling it, writing via set / update.

const count = signal(0);
count(); // read — subscribes the surrounding effect
count.peek(); // read WITHOUT subscribing
count.set(1); // write
count.update((v) => v + 1); // transform
CallEffect
count()Reads the value. Inside an effect the dependency is registered.
count.peek()Reads the value without subscribing.
count.set(v)Sets the value and notifies subscribers.
count.update(fn)Sets fn(current).

An effect runs immediately and again whenever a signal read inside it changes.

effect(() => {
// Every signal read here becomes a dependency.
output.textContent = String(count());
});

If the effect returns a function, it is called before the next run and on disposal — ideal for timers, observers or subscriptions.

effect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id); // cleanup before next run / on dispose
});

computed derives a value from other signals. It updates automatically when a dependency changes.

const price = signal(10);
const qty = signal(3);
const total = computed(() => price() * qty());
total(); // 30
qty.set(4);
total(); // 40

Internally computed(fn) is a signal fed by an effect — it is itself a signal again and can be read in further effects/computeds.

batch bundles multiple writes so dependent effects run once at the end instead of after every set.

import { batch } from "@fullhaus/rivet";
batch(() => {
price.set(20);
qty.set(5);
}); // effects that read price & qty run exactly once

untrack reads signals without subscribing the surrounding effect — useful when an effect should react to one signal but only “read along” another.

import { untrack } from "@fullhaus/rivet";
effect(() => {
// Reacts to `qty`, but NOT to `price`:
const snapshot = untrack(() => price()) * qty();
render(snapshot);
});
  • Reading a signal → inside an effect = subscribe.
  • Signal set → re-run all dependent effects (synchronously, unless batch).
  • Object.is equality prevents unnecessary runs.
  • Effects optionally return a cleanup function.