Overview

onError(fn) registers an error handler on the current component host. When the outer function or template throws during render, every registered handler receives the error. Use it to report (e.g. Sentry) and to flip local atoms that drive fallback UI.

onPaint(fn) runs after each paint of the current component and its children — on initial mount and on every subsequent update. Safe for layout reads (getBoundingClientRect, scroll position) and imperative post-paint work. If fn returns a function, that cleanup runs before the next paint or on unmount.

Both hooks are lifecycle registrations: call them once in the outer function. They are not substitutes for reactive rendering — keep templates pure and push side effects into these hooks.

onError vs onPaint

API
Fires when
Typical use
onError(fn) Render/setup throw Report errors; set atoms for fallback UI. Handler returns void.
onPaint(fn) After every paint Measure layout, sync imperative DOM, start paint-tied animations. Optional cleanup return.

onError — component error boundary

onError catches errors thrown during this component's render or setup. Use the handler to update atoms that drive fallback UI (as shown) and/or report the error.

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

const RiskyComponent = () => {
const hasError = atom(false);
const errorMsg = atom('');

// Register in the outer function — fires when render/effects throw
onError((err) => {
hasError.set(true);
errorMsg.set(err instanceof Error ? err.message : String(err));
});

const triggerError = () => {
throw new Error('Something went wrong!');
};

return () => hasError()
? html`
<div class="m-col m-gap">
<div class="card card-body alert alert-danger">
<span class="tag tag-danger">Error caught</span>
<p class="hint">${errorMsg()}</p>
</div>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button @click=${() => { hasError.set(false); errorMsg.set(''); }}>
Reset
</button>
</div>
</div>
`
: html`
<div class="m-col m-gap m-items-center center">
<p>Click the button to trigger an error — onError catches it.</p>
<button class="btn-danger" @click=${triggerError}>Trigger error</button>
</div>
`;

onPaint — read DOM after paint

onPaint runs after every paint of the component subtree. Use it for layout measurements or post-paint imperative work. Guard reactive writes so you don't loop.

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

const MeasureBox = () => {
const dimensions = atom({ w: 0, h: 0 });

onPaint(() => {
const el = document.getElementById('measured-box');
if (!el) return;
const r = el.getBoundingClientRect();
const w = Math.round(r.width);
const h = Math.round(r.height);
const prev = dimensions();
// Guard — onPaint runs every paint; unconditional set() loops forever
if (prev.w !== w || prev.h !== h) dimensions.set({ w, h });
});

return () => html`
<div class="m-col m-gap">
<div id="measured-box" class="card card-body p-20 center">
<p class="m-0">Resize the preview to see this update.</p>
</div>
<div class="m-grid m-grid-cols-3 m-gap">
<div class="card card-body center">
<span class="overline">Width</span>
<span class="card-title">${dimensions().w}px</span>
</div>
<div class="card card-body center">
<span class="overline">Height</span>
<span class="card-title">${dimensions().h}px</span>
</div>
</div>
</div>
`;
};

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