m.render inside route onmatch causes infinite loop
In the code below, the onmatch function is called over and over again in a loop, and the Component is never actually rendered. I'm new to Mithril so I may just be doing something wrong, but I didn't see anything in the documentation indicating that I shouldn't do this.
const Component = {
view: function() {
return m('div', 'hello world')
}
};
m.route(document.body, '/index', {
'/index': {
onmatch: function() {
console.log('onmatch was called');
// Show loading while we load data
m.render(document.body, m('div', 'loading...'));
return new Promise(function(resolve, reject) {
setTimeout(resolve, 1000);
}).then(function() {
return Component;
});
}
}
})
The issue seems to be with the call to m.render. If you comment that out it works fine.
@SleepyPierre if you choose a different mount point to render the loading div, it'll work.
E.g.: <div id=test></div>
And then: m.render(document.getElementById('test'), m('div', 'loading...'));
@osban That does work, but what I really want is for the loading div to replace the content of the current route while the next route loads. And the only way I can see to do that is if the mount point of the loading div is the same as the mount point of the routes.
If I just change the mount point of the loading div, then the loading would appear next to the content, and it does not go away once the route's content has loaded.
Obviously this is a contrived example and there is only one route, but I think you can see what I'm getting at.
@SleepyPierre I think in that case it's easier to deal with it in the components, something like this. But you can also do it in the Route Resolver, like in the docs example.
That worked, thanks!
I'll still leave this issue open, as I still believe it's a bug. (This worked in an older version, so I'm sure more people will come across this as they upgrade)
At the very least, there should probably be an error message and/or a note in the change log
@SleepyPierre Your component should handle the temporary loading, not the onmatch. Think of it in terms of states. Something like this should work better:
// Make sense of the loading, so you can actually do stuff with it.
// And yes, I've been considering making this a core component:
// https://github.com/MithrilJS/mithril.js/issues/2282
function Async({attrs: {load}}) {
let state = "pending", result
new Promise(resolve => resolve(load())).then(
v => { state = "ready"; result = v },
e => { state = "error"; result = e }
)
return {
view: function ({attrs: {pending, ready, error}) {
if (state === "pending") return pending()
if (state === "ready") return ready(result)
if (state === "error") return error(result)
}
}
}
const Component = {
view: function() {
return m('div', 'hello world')
}
};
m.route(document.body, '/index', {
'/index': {
render: () => m(Async, {
async load() {
await new Promise(resolve => setTimeout(resolve, 1000))
return Component
},
ready: Component => m(Component),
error: e => m("div", ["Whoops! ", e.message]),
}),
}
})
The infinite loop sounds like a bug, though. It should be stepping on m.mount's toes behind the scenes, not generating infinite loops. (m.mount should really be throwing if attempting to mount to a DOM node with a non-empty vnode tree.)