Managing Side Effects with Mithril Component Lifecycle Hooks
Learn how Mithril’s lifecycle hooks (oninit, oncreate, onupdate, onbeforeupdate, onremove) let you fetch data, manage subscriptions, and clean up resources safely.
21 Aug 2025, 19:28 UTC

Why lifecycle hooks matter
In Mithril, a component is a plain JavaScript object that provides a view method and optional lifecycle hooks. The hooks—oninit, oncreate, onupdate, onbeforeupdate, and onremove—are called automatically by the framework at specific moments in the component’s lifetime. By placing side‑effect logic in these hooks you keep the view pure and avoid manual DOM access.
Example: fetching data and cleaning up
const UserProfile = {
oninit(vnode) {
// Store the request promise so we can cancel it later
vnode.state.request = m.request({
method: 'GET',
url: '/api/user/' + vnode.attrs.id,
deserialize: JSON.parse
});
// When the request resolves, update the component state
vnode.state.request.then(data => {
vnode.state.user = data;
m.redraw(); // trigger a redraw because the data changed
});
},
onremove(vnode) {
// If the request is still pending, abort it to prevent memory leaks
if (vnode.state.request && typeof vnode.state.request.abort === 'function') {
vnode.state.request.abort();
}
},
view(vnode) {
if (!vnode.state.user) {
return m('div', 'Loading…');
}
return m('div', [
m('h2', vnode.state.user.name),
m('p', vnode.state.user.email)
]);
}
};
// Mount the component inside a DOM element with id="app"
m.mount(document.getElementById('app'), UserProfile);
How the hooks are invoked
- oninit – runs once before the first DOM creation, ideal for initializing state or starting asynchronous work.
- oncreate – runs after the DOM element has been inserted into the document; safe to interact with the newly created nodes.
- onbeforeupdate – runs before the view is diffed; receives the old and new vnode and can return false to skip the update.
- onupdate – runs after the DOM has been updated; useful for measuring layout or integrating third‑party libraries that depend on the DOM.
- onremove – runs just before the DOM node is removed; the place to cancel timers, abort requests, or detach event listeners.
Limits of the lifecycle API
The hooks are only available for components defined with m.component (the legacy factory) or the ES‑class syntax that Mithril provides (class extends m.Component). A plain view function returned directly by m()—for example m('div', hello)—does not receive lifecycle calls because it is not wrapped as a component instance. If you need lifecycle behavior you must wrap the view in a component object, even if the view itself is simple.
Common mistakes and how to avoid them
- Forgetting to return the view from
oninit–oninitmust not return a virtual DOM tree; returning anything other thanundefinedis ignored and can cause confusion. Keeponinitfocused on state setup and let theviewmethod return the markup. - Mutating state directly without triggering a redraw – Changing
vnode.stateinside a hook does not automatically update the UI. Callm.redraw()(or rely on an asynchronous operation that already triggers a redraw, likem.request) after the mutation. - Assuming
onupdatefires for every attribute change – Mithril skips updates for unchanged children; if you rely ononupdateto run for each prop, you may miss cases where a child component is reused. Useonbeforeupdateto compare specific attrs when you need fine‑grained control. - Performing heavy synchronous work in
oncreateoronupdate– Blocking the UI thread defeats Mithril’s goal of smooth interactions. Offload expensive calculations to Web Workers or defer them withsetTimeoutorPromise.resolve().then. - Neglecting cleanup in
onremove– Subscriptions, timers, or third‑party library instances left active cause memory leaks. Always pair initialization inoninitoroncreatewith corresponding teardown inonremove.
Quick verification steps
- Open a browser console and add a
console.logstatement at the start of each hook in the example component. - Mount the component with
m.mount(document.getElementById('app'), UserProfile). - Observe the log order:
oninit→oncreate→ (optional)onupdateon subsequent changes →onremovewhen you callm.mount(null, root). - Modify a prop (e.g., change the user ID) and confirm that
onupdatefires, then unmount and verifyonremoveruns.
By following the pattern shown—initializing asynchronous work in oninit, updating state with m.redraw, and cleaning up in onremove—you can manage side effects safely while keeping your Mithril views declarative and easy to test.