Diagnosing Missing Mithril Lifecycle Hooks: A Practical Checklist
When a Mithril component’s lifecycle hook doesn’t run, the UI can behave unexpectedly. This guide walks through the recognizable symptom, causes, a diagnostic table, ordered checks, targeted fixes, and escalation steps to resolve missing hook execution.
02 Apr 2026, 03:26 UTC

Recognizable Condition: A Hook Never Fires
When a Mithril component renders but its oninit, oncreate, or onupdate hook never runs, the UI may lack initial state, miss side‑effects, or show stale data. The symptom is usually a silent failure – the component appears, but the expected console logs or DOM changes are absent.
Cause & Diagnostic Table
| Possible Cause | What to Look For |
|---|---|
| Misspelled hook name | Console warning about unknown attribute or missing hook |
Hook defined as arrow function that loses this | Runtime error or undefined when accessing component state |
Component returned from view instead of passed to m() | Hooks not attached; component renders as plain object |
Using m.render with a plain object | Hooks skipped; only view function runs |
| Duplicate component definitions overwriting each other | Only the last definition is used; earlier hooks lost |
Global m overwritten by another library | Hooks not recognized; unexpected behavior |
Ordered Checks
Verify the component is passed to
m()orm.routecorrectly. A common mistake is returning a component object from aviewfunction instead of wrapping it:return Component;should bereturn m(Component, attrs);.Confirm hook names match Mithril v2 spelling. The full set is
oninit,onbeforeupdate,onupdate,onbeforeremove,onremove, andoncreate. Misspellings or missing hooks are ignored silently.Check hook definitions are regular functions. Arrow functions lose the component’s
thiscontext. If a hook accessesthis.state, usefunction (){…}or bind explicitly:oninit: function(){…}.bind(this).Look for duplicate component exports. In a large project, two files may export a component with the same name; the last import wins, wiping earlier hooks. Use a consistent import path and a unique component name.
Inspect the console for warnings. Mithril logs unknown attributes or missing hooks. A warning such as
Unknown attribute: oninitindicates the hook name was not recognized.Verify no global library has overwritten
m. If another script redefineswindow.m, your component hooks will be lost. In devtools, runconsole.log(m)to confirm it points to the Mithril instance.
Fixes Tied to Findings
- Rename or add missing hook with correct spelling. Example:
oninit: function(){…}. - Replace arrow function with a regular function or bind
this. Example:oncreate: function(){ this.element = this.domNode; }. - Wrap the component in
m()before rendering. Example:m.mount(document.body, m(MyComponent, {id: 42})); - Ensure only one component definition per file. Remove duplicate exports or rename components.
- Call
m.redraw()after async state changes. If you fetch data inoninit, trigger a redraw once the promise resolves:m.redraw();.
Escalation Criteria
- If hooks still fail after the above steps, consider upgrading to the latest Mithril 2.x release; newer releases fix subtle virtual‑dom diff bugs.
- Check for conflicting global libraries that overwrite
m; isolate the issue by loading Mithril in a sandboxed iframe. - Create a minimal reproducible example: a single HTML file with a minimal component that demonstrates the missing hook. If it still fails, file a bug on the Mithril GitHub repository with the test case.
- Persistent failures may indicate a bug in the diff algorithm. Provide a snapshot of the component tree and the diff output (via
m.redraw.sync(() => console.log(m.redraw()))) when filing the issue.
Concrete Example: oninit Not Running
// component.js
const Counter = {
// ❌ Mistake: arrow function loses this
oninit: () => {
this.count = 0; // this is undefined
},
view: () => {
return m('div', [
m('p', `Count: ${this.count}`),
m('button', {onclick: () => this.count++}, 'Increment')
]);
}
};
// main.js
m.mount(document.body, Counter);
In the example above, the oninit hook never runs because it’s an arrow function. The this context is undefined, so this.count is never set. The solution is to use a regular function or bind this:
const Counter = {
oninit: function() { this.count = 0; },
view: function() {
return m('div', [
m('p', `Count: ${this.count}`),
m('button', {onclick: () => this.count++}, 'Increment')
]);
}
};
Verification Methods
Open devtools console and add
console.log('oninit fired');at the start of the hook. Trigger a route change or callm.mountagain; the log should appear.Use
m.redraw.sync(() => { console.log('redraw sync'); });right afteroninitto confirm synchronous rendering.In TypeScript, compile with
--noEmitand inspect the emitted JS to ensure hook names are not mangled by a minifier.Compare the component definition against the official Mithril 2.x API checklist. All required properties should be present and correctly named.
Limitations
These checks assume you are using Mithril 2.x. In Mithril 1.x, lifecycle hooks are limited to oncreate and onupdate; oninit does not exist. If you are on an older version, upgrade or adjust your expectations accordingly.
Also, defining hooks inside the view function recreates them on every render, which can cause performance issues and unexpected behavior. Keep hooks at the component’s top level.
Summary
When a Mithril lifecycle hook fails to execute, follow the ordered checks: confirm component passing, verify hook names, ensure proper function context, eliminate duplicate definitions, and watch for global overrides. Apply the targeted fixes, and if the problem persists, consider upgrading or isolating the issue in a minimal example before escalating to the community.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.