Using Mithril’s m.request for Simple Data Fetching in SPAs
Learn how Mithril’s m.request simplifies data fetching by combining promises, automatic redraw, and unified error handling in a single call.
07 Oct 2025, 16:32 UTC

Problem: keeping the view in sync after an HTTP call
In a single‑page application built with Mithril, a common task is to load data from a REST endpoint and display it. If you use the low‑level fetch API directly, you must remember to call m.redraw() after the promise resolves or rejects; otherwise the UI shows stale data. Forgetting this step leads to inconsistent views and extra boilerplate.
Thesis: m.request bundles the request, automatic redraw, and error handling
Mithril’s m.request utility wraps fetch (or an XHR polyfill for older browsers) and returns a native promise. When that promise settles, Mithril automatically triggers a redraw, so the view updates without any manual m.redraw() call. The same mechanism also propagates rejection reasons to the component’s onerror handler, giving you a single place to handle HTTP errors.
How it works
Promise‑driven auto‑redraw
When you invoke m.request(options), Mithril:
- Creates a promise that resolves with the parsed response body (or rejects on network/HTTP errors).
- Attaches a
.thenhandler that callsm.redraw()on settlement. - Returns the original promise so you can still chain
.thenor.catchif you need custom logic.
Because the redraw is tied to the promise settlement, you never have to remember to call m.redraw() yourself.
Configurable options
The options object mirrors the familiar fetch arguments but adds a few Mithril‑specific keys:
method– HTTP verb (default:GET).url– endpoint.headers– object of request headers.data– body payload; automatically serialized unless you provide a customserializefunction.deserialize– function to transform the raw response text (default: JSON.parse).background– iftrue, suppresses the automatic redraw; you must callm.redraw()manually.initialValue– value shown in the view while the request is pending.
Unified error handling
If the promise rejects, the error propagates to the nearest component’s onerror handler. You can also catch it with .catch on the returned promise. This lets you display a generic error banner or fallback UI without scattering try/catch blocks throughout your code.
Worked example: fetching a list of users
The following component demonstrates the typical usage. Place this code in a file like users.js and mount it with m.mount.
const Users = {
oninit: function(vnode) {
// start with an empty array while loading
vnode.state.users = [];
vnode.state.error = null;
// fetch users; m.request returns a promise
m.request({
method: 'GET',
url: 'https://api.example.com/users',
// optional: customize headers
headers: { 'Accept': 'application/json' },
})
.then(function(result) {
// on success, store the data
vnode.state.users = result;
})
.catch(function(err) {
// on any network or HTTP error, capture it
vnode.state.error = err;
});
},
view: function(vnode) {
if (vnode.state.error) {
return m('.error', 'Failed to load users: ' + vnode.state.error.message);
}
return [
m('h2', 'User List'),
m('ul', vnode.state.users.map(function(user) {
return m('li', user.name);
}))
];
}
};
What happens:
- During
oninit, the component sets an emptyusersarray and callsm.request. - Mithril sends the GET request using the native
fetchAPI (or XHR polyfill). - When the promise settles, Mithril automatically calls
m.redraw(), causing theviewto re‑run. - If the request succeeds, the view renders the list; if it fails, the error message is shown.
- No explicit
m.redraw()is needed.
Trade‑offs and limitations
Background requests
Setting background: true disables the automatic redraw. This is useful when you want to fire a request that does not affect the current UI (e.g., prefetching data for a future route). Remember: you must call m.redraw() yourself after the promise settles, otherwise the view will stay stale.
Large payloads and streaming
m.request buffers the entire response body before invoking deserialize. For very large JSON payloads, binary file uploads, or when you need progress events, consider using the lower‑level fetch or XMLHttpRequest directly, or supply custom serialize/deserialize functions that work with streams.
Actionable closing
If you are building a Mithril SPA and need to fetch data, start with m.request. It gives you a promise‑based API, automatic view updates, and centralized error handling with minimal code. Only opt out of the automatic redraw (via background) or drop to the raw fetch API when you have specific streaming or progress‑reporting requirements.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.