Fetching Data in Mithril.js v2: Using oninit and When Manual Redraw Is Unnecessary
Learn how to fetch data in Mithril.js v2 using oninit, rely on automatic redraw, and avoid common pitfalls.
21 Dec 2025, 21:16 UTC

Problem: Data appears stale after the initial load
When a Mithril component renders a list from an API, the UI often shows the old values until you manually call m.redraw(). This extra step is easy to forget and can introduce bugs, especially in larger apps.
Thesis
The oninit lifecycle hook together with Mithril’s automatic redraw after m.request settles lets you fetch data once and have the view update automatically, without any explicit redraw call.
Lifecycle hook oninit for data fetching
In Mithril 2.x the oninit hook runs after the component is instantiated but before the first redraw. It is the appropriate place to start an asynchronous request because the component’s state is already attached to the virtual DOM.
Key points:
oninitreceives the virtual node (vnode) so you can read route parameters fromvnode.attrsif needed.- The hook runs only once per mount, making it ideal for one‑time data loads.
- Returning a promise from
m.requestlets you handle success and failure cleanly.
Automatic redraw after m.request resolves
Mithril automatically schedules a redraw after a request promise resolves or rejects. This means the component’s view is updated as soon as the data arrives, provided you do not interfere with the redraw cycle.
Why manual m.redraw() is usually unnecessary:
- Event handlers, including callbacks passed to
m.request, are wrapped by Mithril so the framework knows when a DOM change has occurred. - Calling
m.redraw()inside such callbacks can cause double‑rendering and may break the diffing algorithm.
When manual redraw is unnecessary
Consider the following scenario:
- The component mounts and calls
m.requestinoninitto load a list. - The promise resolves, Mithril updates the virtual DOM, and schedules a redraw.
- The view reflects the new data without any extra code.
If you were to invoke m.redraw() manually after the promise resolves, you would force an additional render pass that serves no purpose and could waste CPU cycles.
Worked example
Below is a minimal component that fetches a list of items from /api/items and displays them. The example follows the guidelines above.
// main.js
import m from "mithril";
const ItemList = {
// oninit runs once when the component is created
oninit(vnode) {
// Start the request; the promise is stored for error handling
m.request({
url: "/api/items",
method: "GET",
// optional: include headers, credentials, etc.
}).then(data => {
// Store data on the component's scope
vnode.state = { data };
}, err => {
console.error("Failed to load items:", err);
vnode.state = { error: err.message };
});
},
view(vnode) {
const state = vnode.state || {};
if (state.error) {
return m("p", { class: "error" }, state.error);
}
if (!state.data || state.data.length === 0) {
return m("p", "No items found.");
}
return m(
"ul",
state.data.map(item => m("li", item))
);
}
};
// Mount the component into a container with id="app"
m.mount(document.getElementById("app"), ItemList);
Explanation of the example:
oninitinitiates a GET request to/api/items. The URL is a placeholder; replace it with your actual endpoint.- The request’s promise is handled in
.thenand.catchto update component state safely. - The
viewfunction readsvnode.stateto render the list or an error message. - No call to
m.redraw()appears anywhere; Mithril handles the redraw automatically after the request settles.
Trade‑off / limitation
Mutating vnode.attrs directly can break Mithril’s diffing because the framework expects that object to be read‑only. If you need to change route parameters or other attributes, create a new vnode or use the router’s API instead of editing vnode.attrs in place.
Another limitation is browser‑only APIs (e.g., window, localStorage) used inside oninit. If the same component code might run in a non‑DOM context (e.g., during server‑side rendering), guard those calls with a runtime check such as if (typeof window !== "undefined").
Actionable closing
To verify that your component updates automatically:
- Open a browser with Mithril 2.x loaded (check
m.versionorpackage.json). - Mount the component into a container you can see.
- Open the developer console, set a breakpoint or log inside the
.thenhandler, and watch the list update without invokingm.redraw(). - If the list does not change, inspect the network tab to ensure the request succeeded and that no uncaught promise rejection errors appear.
By following the oninit pattern and trusting Mithril’s automatic redraw, you keep your code concise, reduce the chance of stale UI, and avoid unnecessary re‑renders.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.