Skip to content

Lifecycle & Cleanup

Rivet cleans up after itself. Every listener, effect, event subscription and manual cleanup registered on the context is called on unmount — no dangling timers, no leaks.

mount(root?) scans root (default: document) for [data-rivet] and instantiates every not-yet-mounted component.

const app = createRivet().component("counter", counter);
app.mount(); // entire document
app.mount(container); // only inside an element

mount() is idempotent: already-mounted elements are skipped. You can call it again after dynamically loading HTML to pick up new roots.

unmount() disposes all instances of the app and calls their collected cleanups. IDs are released.

app.unmount();

What happens automatically on unmount:

  • on(...) listeners are removed via removeEventListener
  • effect(...) and computed(...) are stopped (incl. their effect cleanups)
  • listen(...) / global.listen(...) are unsubscribed
  • every function registered with cleanup(fn) runs

For your own resources (observers, timers, external subscriptions) register a cleanup function via cleanup:

defineComponent(({ root, cleanup }) => {
const observer = new IntersectionObserver(/* … */);
observer.observe(root);
cleanup(() => observer.disconnect());
return {};
});

Alternatively, an effect can return its cleanup function — it runs in addition before each re-run of the effect:

effect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
});

The app implements Symbol.dispose. With the using statement (TypeScript 5.2+, node >= 24 / modern runtimes) it is disposed automatically at the end of the block:

{
using app = createRivet().component("counter", counter);
app.mount();
// … work …
} // app[Symbol.dispose]() → unmount() runs automatically

The top-level effect export also returns a Dispose (callable and Symbol.dispose-capable):

import { effect } from "@fullhaus/rivet";
const stop = effect(() => { /* … */ });
stop(); // stop manually

Typical pattern with dynamically loaded HTML (e.g. AJAX navigation in TYPO3):

async function loadInto(container: HTMLElement, url: string) {
container.innerHTML = await fetch(url).then((r) => r.text());
app.mount(container); // pick up new [data-rivet] roots
}