Search code examples
javascriptmithril.js

How to escape infinite loop when loading from a file in a layout component?


I am building a personal webpage with Mithril JS by following the simple application example in https://mithril.js.org/simple-application.html and the layout example in http://lhorie.github.io/mithril-blog/better-templates-with-fp.html. But I keep running into an infinite loop with a component that needs to load data from a file.

The layout component doesn't loop if I pass an inner file-loading component to it via "vnode.attrs". However, it loops if I build the layout component using the inner component in a function. I'm unable to understand the difference.

Failing example:

hello.json

{"text": "hello"}

index.html

<!DOCTYPE html>
<body>
    <script src="https://unpkg.com/mithril/mithril.js"></script>
    <script src="index.js"></script>
</body>

index.js

var Hello = {
    loadedObj: {},
    load: function () {
        return m.request({
            method: "GET",
            url: "hello.json",
        }).then(function (result) { Hello.loadedObj = result })
    }
}

var HelloBox = {
    oninit: Hello.load,
    view: function () { return m("div", {}, Hello.loadedObj.text) }
}

var layout = function (comp) {
    return {
        view: function () { return m('div', {}, m(comp)) }
    }
}

var workingLayout = {
    view: function (vnode) { return m('div', {}, m(vnode.attrs.comp)) }
}

m.route(document.body, "/workinghello", {
    "/hello": {
        render: function () {
            console.log("rendering /hello")
            return m(layout(HelloBox))
        }
    },
    "/workinghello": {
        render: function () {
            console.log("rendering /workinghello")
            return m(workingLayout, { comp: HelloBox })
        }
    }
})

Here, the route "/workinghello" works, but "/hello" gets into a loop. Why? The "/workinghello" design seems like a code smell to me as "vnode.attrs" is generally used only to pass data to components in the documentation and not components themselves. Is there a way to fix "/hello" or simplify "/workinghello"?


Solution

  • The issue was my misunderstanding of layout components and how m.render behaves. This precise behavior is addressed in the "Wrapping a Layout Component" section of the [documentation](https://mithril.js.org/route.html#advanced-component-resolution}. The response from Hello.load triggers a redraw, which calls renders the /hello route again, causing an infinite loop. I was able to clean up the routing with the following design (as suggested in the doc link). If the layout component is defined as:

    var fixedLayout = {
        view: function(vnode) {
            return vnode.children
        }
    }
    

    and the router uses the component like so:

        "/fixedhello": {
            render: function() {
                return m(fixedLayout, m(HelloBox))
            }
        }
    

    there is no loop.