REFERENCE / CORE API
onCleanup()
Registers teardown for the current scope. It runs before every re-run of the owning effect, and once more when the scope is disposed for good — children before parents.
Signature
onCleanup(fn: () => void): void
Parameters
PARAMETERTYPEDESCRIPTION
fn
() => void
The teardown. Called synchronously at the moments above; it may be registered any number of times in a scope.
In a component
A component function is a scope. Anything it starts — timers, observers, subscriptions, third-party widgets — is released here when the component is removed by a conditional, a list, or an explicit dispose.
function Clock() {
const now = signal(new Date());
const id = setInterval(() => (now.value = new Date()), 1000);
onCleanup(() => clearInterval(id)); // when the Clock's scope dies, so does the interval
return html`<time>${() => now.value.toLocaleTimeString()}</time>`;
}
In an effect
Inside an effect, cleanup also runs before each re-run, so the previous run's resources never leak into the next.
effect(() => {
const el = target.value; // which element to watch
const ro = new ResizeObserver(report);
ro.observe(el);
onCleanup(() => ro.disconnect()); // before the effect re-runs for a new target, and on dispose
});
Where it is legal
Anywhere a scope is active: a component function, an effect, a root(), a list row. mount() wraps its component in an app-lifetime root, so a top-level component may use it too. Outside any scope there is nothing to attach to.
Try it
import { signal, effect, onCleanup, html, mount } from '@amojs.dev/core';
const ms = signal(1000);
const pulses = signal(0);
const log = signal<string[]>([]);
const note = (line: string) => (log.value = [...log.peek(), line]);
// the effect re-runs when `ms` changes; onCleanup runs BEFORE each re-run, so the old timer never leaks
effect(() => {
const period = ms.value; // captured: by the time cleanup runs, ms already holds the NEW value
const id = setInterval(() => pulses.value++, period);
note(`setup: every ${period} ms`);
onCleanup(() => {
clearInterval(id);
note(`cleanup: the ${period} ms timer`);
});
});
const Demo = () => html`<div class="demo">
<p>pulses: <b>${pulses}</b></p>
<div class="row">
<button class="counter" onclick=${() => (ms.value = 250)}>every 250 ms</button>
<button class="counter" onclick=${() => (ms.value = 1000)}>every 1000 ms</button>
</div>
<ul class="log">${() => log.value.map((line) => html`<li>${line}</li>`)}</ul>
</div>`;
const host = document.getElementById('demo');
if (host) mount(Demo, host);