Search code examples
javascripthtmldommithril.js

Creating new elements at runtime using Mithril


Using Mithril, a Javascript framework, I am trying to add new elements after the initial body has been created and rendered.

Here is my problem in it's most basic form:

let divArray = [];
let newDivButton = m('button', { onclick: ()=> {
    divArray.push(m('div', "NEW DIV PUSHED"));
}}, "Add new div");

divArray.push(newDivButton);
divArray.push(m('div', "NEW DIV PUSHED"));

let mainDiv = {
    view: () => {
        return divArray;
    }
}

m.mount(document.body, mainDiv);

The code above will create a button and one line of text saying NEW DIV PUSHED. The button adds one new text element exactly like the 1st one. The problem here is that those additional elements are simply not rendered even if the view function is called. I stepped in the code and clearly see that my divArray is being populated even if they are not rendered.

One thing I noticed is that the initial text element (the one that is rendered) has it's dom property populated by actual div object. All the subsequent text elements in my array have their dom property set to undefined. I don't know how to fix this but I am convinced it is related to my problem.


Solution

  • Mithril has an optimization built into the render lifecycle - it won't re-render a DOM tree if the tree is identical to the last tree. Since divArray === divArray is always true the nodes are never re-rendering.

    The simple, but non-ideal solution is to slice your array so you're always returning a new array from mainDiv#view and therefore, Mithril will always re-render the top-level array:

    let mainDiv = {
      view: () => {
        return divArray.slice();
      }
    };
    

    The more correct way to do this is to map over the data, creating vnodes at the view layer, rather than keeping a list of vnodes statically in your module scope:

    let data = ["Data Available Here"];
    let mainDiv = {
      view: () => {
        return [
          m(
            'button',
            { onclick: () => data.push("Data Pushed Here") },
            "Add new div"
          );
        ].concat(
          data.map(datum => m('div', datum))
        );
      }
    };