Understanding Mithril’s Auto‑Redraw Mechanism: Architecture and Practical Use
Explore how Mithril’s auto‑redraw wraps async work, schedules updates with requestAnimationFrame, and lets developers control redraw frequency for performance.
23 Sept 2026, 13:34 UTC

Requirements
Mithril’s auto‑redraw feature must keep the view in sync with model changes that happen as a result of asynchronous work (e.g., network requests, timers, DOM events) without requiring the developer to call m.redraw() manually. It should also allow developers to suppress or batch redraws when performance is critical.
Smallest Suitable Design
The design consists of three parts:
- Wrap – All Mithril‑provided asynchronous APIs (
m.request,m.routeresolvers,onmatchhooks) and DOM event handlers are wrapped so that, after the wrapped promise settles or the event callback finishes, a flag is set indicating that a redraw is needed. - Schedule – At the end of the current microtask queue, Mithril checks the flag. If set, it schedules a view update using
requestAnimationFrame, ensuring the redraw happens just before the browser paints. - Control – Developers can temporarily disable the flag with
m.redraw.sync(false)or bracket code withm.startComputation()/m.endComputation()to batch multiple state changes into a single redraw.
Trust/Data Boundaries
The auto‑redraw system trusts only the asynchronous entry points that Mithril itself wraps. Direct DOM mutations or state changes performed outside those wrappers (e.g., using element.textContent = … from plain JavaScript) do not trigger the flag, so the view may become stale unless the developer explicitly calls m.redraw(). Conversely, any change made through Mithril’s wrappers is guaranteed to be observed by the redraw scheduler.
Operational Checks
To verify that the mechanism works as expected in a component:
- Create a component that fetches data in
oninitusingm.request. - Log a message in
oncreateand again inonupdate. - After the request resolves, you should see the
onupdatelog, indicating the view redrew. - For manual control, wrap a series of rapid state changes with
m.redraw.sync(false)before the changes andm.redraw.sync(true)after; only oneonupdatelog should appear.
Failure Modes
- Excessive redraws – If asynchronous calls are issued inside a tight loop without batching, each settlement will set the redraw flag, potentially causing a redraw per iteration. This can be mitigated by using
m.redraw.syncor moving the loop outside of asynchronous boundaries. - Stale view from direct DOM changes – Mutations that bypass Mithril’s wrappers do not set the flag, so the view will not update until the next automatic redraw from another source or an explicit
m.redraw()call. - Missed redraw if flag cleared early – If a developer mistakenly calls
m.redraw.sync(false)and never restores sync, the view will never redraw again until sync is re‑enabled.
Conditions That Would Change the Design
The current design assumes a browser environment with a functional requestAnimationFrame and a microtask‑based event loop (as in modern browsers). If Mithril were to target a non‑browser runtime (e.g., server‑side rendering or a native UI bridge) where those primitives are absent or behave differently, the scheduling step would need to be replaced with an appropriate flush mechanism (e.g., a custom tick callback). Additionally, if the framework introduced a new primitive that bypasses the existing wrappers (such as a low‑level mutable store), the trust boundary would shift and the auto‑redraw logic would have to be extended to observe that primitive.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.