Overview

onMount(fn) registers a callback that runs once after the component's DOM is attached and ready. Use it for subscriptions, timers, focus, measuring elements, and any side effect that needs a live DOM node.

If the sync callback returns a function, that cleanup runs automatically on unmount — the same as calling onCleanup(). Prefer the return-cleanup form when setup and teardown are paired; use onCleanup() when teardown is defined separately or when the mount callback is async.

Async onMount is supported. On the client it runs after DOM-ready like sync; on SSR async mounts are awaited before HTML is serialized so the first paint can include fetched data. Async callbacks cannot return a cleanup — register teardown with onCleanup().

onMount vs onCleanup

API
When it runs
Notes
onMount(fn) Once after DOM ready Sync or async. Sync may return a cleanup; async cannot.
onMount(() => cleanup) Mount + unmount Returned function is registered as cleanup — equivalent to a separate onCleanup.
onCleanup(fn) On unmount Call multiple times; all run in registration order. Use for async mounts or split teardown.

Auto-start timer with cleanup

onMount fires once after the component is attached to the DOM. Returning a function from the callback registers it as a cleanup — called automatically when the component unmounts.

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

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

onMount(() => {
const id = setInterval(() => seconds.set(n => n + 1), 1000);
// Return cleanup — called automatically on unmount
return () => clearInterval(id);
});

return () => html`
<h1>${seconds()}s</h1>
<p class="label">Timer auto-starts on mount, cleans up on unmount</p>
`;
};

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

onMount + explicit onCleanup

Use onCleanup() when teardown is defined separately from setup — for example when it depends on a value created outside of onMount.

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

const Logger = () => {
const log = atom(['Component mounted!']);

onMount(() => {
log.set(prev => [...prev, 'onMount ran']);
});

onCleanup(() => {
// This would log on unmount (not visible in iframe but shown in code)
console.log('Logger cleaned up');
});

const addEntry = () => {
log.set(prev => [...prev, `Entry ${prev.length}`]);
};

return () => html`
<div class="m-col m-gap">
<div class="card card-body">
<span class="overline">Lifecycle log</span>
${log().map(e => html`<div class="mono">✓ ${e}</div>`)}
</div>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${addEntry}>Add entry</button>
</div>
</div>
`;
};

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