Overview

Lifecycle hooks run at precise points in a component's life. Register them in the outer function (setup) — never inside the template or an effect. See Mental Model.

Order: outer setup → first render → onMount (DOM ready) → reactive updates → onPaint after each paint → unmount → onCleanup. onAllMount waits for this host and every descendant.

Atoms, effects, and framework hooks are disposed automatically on unmount. You only tear down what you created yourself (timers, third-party listeners, sockets).

Choose a lifecycle API

API
When
Prefer when
onMount Once after DOM ready Timers, focus, fetch-on-mount. Sync may return cleanup.
onCleanup On unmount Explicit teardown; required for async mounts.
onPaint After every paint Layout reads / post-paint work. Guard atom writes.
onError Render/setup throw Report + drive fallback UI via atoms.
ref / setRef DOM handle Imperative element access after attach.
onAllMount Subtree mounted Analytics / layout that needs children ready.

onMount with cleanup

Register once in setup. Returning a function from sync onMount tears down on unmount.

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

const Timer = () => {
const n = atom(0);

onMount(() => {
const id = setInterval(() => n.set(x => x + 1), 1000);
return () => clearInterval(id);
});

return () => html`
<div class="m-col m-gap m-items-center">
<h1>${n()}s</h1>
<p class="label">onMount + cleanup return</p>
</div>
`;
};

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

onPaint — measure after paint

Fires after every paint. Compare before atom.set to avoid loops.

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

const Box = () => {
const size = atom('—');

onPaint(() => {
const el = document.getElementById('box');
if (!el) return;
const r = el.getBoundingClientRect();
const next = Math.round(r.width) + '×' + Math.round(r.height);
if (size() !== next) size.set(next);
});

return () => html`
<div class="m-col m-gap">
<div id="box" class="card card-body p-20 center">Measure me</div>
<span class="tag">${size()}</span>
</div>
`;
};

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