Skip to content

Signals

Signals are Whisq’s reactive primitives. They hold values that automatically track dependencies and trigger updates when changed.

import { signal } from "@whisq/core";
const count = signal(0);
count.value; // 0 — read (triggers dependency tracking)
count.value = 5; // write (triggers updates)
count.update(n => n + 1); // 6 — update via function
count.peek(); // 6 — read WITHOUT tracking
count.set(10); // 10 — alias for direct assignment

Use peek() inside effects when you need to read a value without creating a dependency:

effect(() => {
// Re-runs when `trigger` changes, but NOT when `config` changes
console.log(trigger.value, config.peek());
});

computed() creates a read-only signal that auto-updates when its dependencies change:

import { signal, computed } from "@whisq/core";
const firstName = signal("Ada");
const lastName = signal("Lovelace");
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
fullName.value; // "Ada Lovelace"
firstName.value = "Grace";
fullName.value; // "Grace Lovelace" — auto-updated

Computed values are lazy — they don’t recompute until read. They also cache — reading twice without dependency changes returns the cached value.

effect() runs a function immediately and re-runs it whenever its dependencies change:

import { signal, effect } from "@whisq/core";
const count = signal(0);
const dispose = effect(() => {
console.log(`Count is: ${count.value}`);
});
// Logs: "Count is: 0"
count.value = 1;
// Logs: "Count is: 1"
dispose(); // Stop watching
count.value = 2; // No log — effect is disposed

Return a function from an effect to run cleanup before each re-execution:

effect(() => {
const timer = setInterval(() => tick(), 1000);
return () => clearInterval(timer); // cleanup
});

Effects only track signals read in the current execution:

const flag = signal(true);
const a = signal("A");
const b = signal("B");
effect(() => {
// When flag is true, tracks `a` only
// When flag is false, tracks `b` only
console.log(flag.value ? a.value : b.value);
});

batch() defers effect re-runs until all updates complete:

import { signal, effect, batch } from "@whisq/core";
const x = signal(0);
const y = signal(0);
effect(() => {
console.log(`${x.value}, ${y.value}`);
});
// Logs: "0, 0"
batch(() => {
x.value = 1;
y.value = 2;
});
// Logs: "1, 2" — only ONCE, not twice

Without batch(), the effect would run once for x and again for y.

For integrating with external systems:

const count = signal(0);
const unsub = count.subscribe(value => {
// Called immediately with current value, then on every change
externalSystem.update(value);
});
unsub(); // Stop subscribing
  • Elements — Use signals in reactive UI elements
  • Components — Encapsulate signals in components