Overview

on(callback, deps) subscribes to each dep via __subscribe and runs callback (scheduled via the effect scheduler) whenever any listed dep notifies. If callback returns a function, that cleanup runs before the next invocation and on final teardown.

Unlike effect, on does not run on create — only when a dependency fires. Dependencies are explicit; reads inside the callback are not auto-tracked for scheduling.

watch is a backward-compatible alias for on. Call on in the component outer function so unmount unsubscribes automatically. The return value is a cleanup function you can also call manually.

on vs effect

API
Deps
Prefer when
on(fn, deps) Explicit array You want a stable dep list; no run on mount; component-scoped.
effect(fn) Auto-tracked Immediate first run; deps discovered from reads; OK at module level.

Explicit deps with on()

Log stays empty until the first count.set() — on() does not run on create.

import { html, atom, on, renderApp } from 'mates';

const App = () => {
const count = atom(0);
const log = atom([]);

// Explicit deps — first entry appears only after a notify
on(() => {
log.set(prev => [...prev.slice(-4), `count → ${count()}`]);
}, [count]);

return () => html`
<div class="m-col m-gap">
<h1>${count()}</h1>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${() => count.set(n => n + 1)}>+1</button>
<button @click=${() => count.set(0)}>Reset</button>
</div>
<div class="card card-body">
<span class="overline">on() log (empty until first set)</span>
${log().map(e => html`<div class="mono">${e}</div>`)}
</div>
</div>
`;
};

renderApp(App, document.getElementById('app'));

Cleanup when deps change

Return clearInterval from on() — cleanup runs before the next callback and on unmount. Click Resume to fire the first notify.

import { html, atom, on, renderApp } from 'mates';

const App = () => {
const enabled = atom(false);
const ticks = atom(0);

// Runs only when enabled notifies — click Resume to start
on(() => {
if (!enabled()) return;
const id = setInterval(() => ticks.set(t => t + 1), 400);
return () => clearInterval(id);
}, [enabled]);

return () => html`
<div class="m-col m-gap">
<h1>${ticks()}</h1>
<p class="muted m-t-5">${enabled() ? "Ticking…" : "Paused (on idle until notify)"}</p>
<button class="btn-primary" @click=${() => enabled.set(e => !e)}>
${enabled() ? "Pause" : "Resume"}
</button>
</div>
`;
};

renderApp(App, document.getElementById('app'));