Mithril auto-redraw and manual m.redraw() boundaries in async state updates
0 reputation · 13 Nov 2023, 01:35 UTC
0 reputation · 13 Nov 2023, 01:35 UTC
Mithril.js keeps views synchronized with state through an automatic redraw system that activates after DOM events and AJAX requests. When state changes occur outside those boundaries—inside setTimeout callbacks, WebSocket message handlers, or other external event listeners—developers commonly call m.redraw() to trigger reconciliation. The exact boundary where these two mechanisms overlap remains a practical concern.
Because Mithril rerenders the component tree by comparing new virtual DOM output against the previous tree rather than tracking discrete property mutations, every redraw carries a cost proportional to tree size. A known behavioral characteristic is that invoking m.redraw() immediately before an automatic redraw can cause double-renders, but the precise scheduling conditions that produce this behavior are not always obvious in complex asynchronous chains.
For teams integrating Mithril with external event sources, the unresolved question is how to coordinate manual redraws with the framework's internal lifecycle without introducing redundant renders or stale views. Version differences in redraw scheduling may further affect the correct integration approach.
31775 reputation · 13 Nov 2023, 05:17 UTC
1. Timing conditions for coalescing: If a m.redraw() is invoked inside the same JavaScript tick that already contains an automatic redraw trigger (for example, a DOM event or an AJAX callback), Mithril’s internal queue collapses both requests into a single reconciliation pass. The collapse occurs because Mithril schedules the actual rendering after the current task finishes. If the manual call happens after the queued automatic redraw has already been processed, Mithril will schedule a second redraw, and two renders will occur sequentially.
2. Diffing with intermediate trees: When both triggers fire in the same tick, only one virtual‑DOM diff is performed: Mithril builds the *final* tree once and compares it against the previous snapshot. Any intermediate tree that would have existed if the two redraws were separate is never materialised. If the manual call occurs after the first render, Mithril builds a second tree from the already‑updated state and diffs it against the tree produced by the first render, resulting in a second pass.
3. Integration boundaries that need manual coordination: External sources that do not originate from Mithril’s event system—setTimeout, setInterval, WebSocket callbacks, native addEventListener on DOM nodes, or any third‑party library—must explicitly trigger a redraw. Use m.redraw() or wrap state changes in m.startComputation()/m.endComputation() to guarantee a single diff, even if m.redraw() is called inside. Avoid calling m.redraw() twice in the same logical flow; if you must, wrap the first call in m.startComputation() to collapse them.
setTimeout or WebSocket messages, call m.redraw() once after all state mutations.m.redraw.strategy('async') to defer the actual render to the next animation frame.m.startComputation() / m.endComputation() around a batch of mutations to guarantee a single diff, even if m.redraw() is called inside.view() calls; one log per tick indicates coalescing, two logs mean a double render.To fine‑tune the recommendation, could you confirm whether you are using Mithril 2.x or 3.x and if you have changed the default redraw strategy from 'sync' to 'async'?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.