Signals
Signals are the reactive primitive at the heart of Weblisk. They provide fine-grained reactivity — when a signal changes, only the code that depends on it re-runs. No virtual DOM, no diffing, no re-renders.
Creating a Signal
import { signal } from 'weblisk'; const [count, setCount] = signal(0); // Read console.log(count()); // 0 // Write setCount(5); console.log(count()); // 5
signal() returns a tuple: a getter function and a setter
function.
Effects
An effect runs a function whenever its tracked signals change:
import { signal, effect } from 'weblisk'; const [name, setName] = signal('World'); effect(() => { document.title = `Hello, ${name()}!`; }); setName('Weblisk'); // title updates to "Hello, Weblisk!"
Effects automatically track which signals were read during execution and re-run when any of them change.
Cleanup
effect() returns a dispose function:
const dispose = effect(() => { console.log(name()); }); dispose(); // Stop tracking, stop re-running
Computed Signals
A computed signal derives its value from other signals. It's lazy (only recalculated when read) and cached:
import { signal, computed } from 'weblisk'; const [price, setPrice] = signal(100); const [tax, setTax] = signal(0.1); const total = computed(() => price() * (1 + tax())); console.log(total()); // 110 setTax(0.2); console.log(total()); // 120
Store
A store is a reactive object — each property is a signal under the hood:
import { store, effect } from 'weblisk'; const user = store({ name: 'Alice', age: 30 }); effect(() => { console.log(`${user.name} is ${user.age}`); }); user.name = 'Bob'; // Logs: "Bob is 30" user.age = 31; // Logs: "Bob is 31"
Stores use a Proxy so you can read/write properties with normal syntax.
Advanced
Peeking (Non-Tracking Read)
Read a signal without creating a dependency:
const [count, setCount] = signal(0); effect(() => { // count.peek() doesn't track — this effect won't re-run when count changes console.log('Current count:', count.peek()); });
Subscribing
Listen to changes outside of effects:
const [count, setCount] = signal(0); const unsub = count.subscribe((value) => { console.log('Changed to:', value); }); setCount(1); // Logs: "Changed to: 1" unsub(); // Stop listening
Patterns
Derived State
const [items, setItems] = signal([]); const count = computed(() => items().length); const empty = computed(() => items().length === 0);
Toggle
const [open, setOpen] = signal(false); const toggle = () => setOpen(!open());
DOM Binding
enhance('[data-island="search"]', (el, { $, effect }) => { const [query, setQuery] = signal(''); const input = $('input'); const results = $('.results'); input.addEventListener('input', (e) => setQuery(e.target.value)); effect(() => { results.textContent = query() ? `Searching: ${query()}` : ''; }); });