REFERENCE / CORE API
root()
Runs fn inside a fresh, detached ownership scope: nothing created in it attaches to the current owner, reads inside it do not subscribe, and the caller gets the dispose function. Nothing disposes it automatically.
Signature
root<T>(fn: (dispose: () => void) => T): T
Parameters
fn
(dispose) => T
Runs immediately. Receives the scope's dispose function; whatever it returns is passed through — usually the dispose function itself, or an object that holds it.
Returns
Whatever fn returned.
Ownership
Every effect belongs to the scope that created it, and dies with it. That is right inside components, where a re-run above should tear a branch down. It is wrong for things meant to outlive their creator — a timer started from a click handler, a subscription an app keeps for its lifetime. root() is the escape: a scope with no parent, owned by whoever holds its dispose function.
const dispose = root((dispose) => {
effect(() => console.log(count.value)); // belongs to this root, not to whatever called root()
return dispose;
});
dispose(); // the effect and everything else created inside are gone
A disposable app
mount() already wraps a component in an app-lifetime root. Wrap the mount in your own root when the app must be removable.
// an app you can tear down — tests, micro-frontends, a widget that comes and goes
const stop = root((dispose) => {
mount(App, document.getElementById('app'));
return dispose;
});
Used by each()
Every row of an each() list renders inside its own detached root, which is why reordering the list never disposes a row and removing one key disposes exactly its own scope.
Try it
import { signal, root, onCleanup, html, mount } from '@amojs.dev/core';
const ticks = signal(0);
const status = signal('stopped');
let dispose: (() => void) | undefined;
const start = () => {
stop();
// a detached scope: nothing above can dispose it by accident, and the caller owns the handle
dispose = root((d) => {
const id = setInterval(() => ticks.value++, 400);
onCleanup(() => clearInterval(id)); // runs when d() is called
status.value = 'running';
return d;
});
};
const stop = () => {
dispose?.();
dispose = undefined;
status.value = 'stopped';
};
const Demo = () => html`<div class="demo">
<p>ticks: <b>${ticks}</b> · <span class="status">${status}</span></p>
<div class="row">
<button class="counter" onclick=${start}>root() + setInterval</button>
<button class="counter" onclick=${stop}>dispose()</button>
</div>
</div>`;
const host = document.getElementById('demo');
if (host) mount(Demo, host);