Hyperhtml: Uncaught DOMException: Failed to execute 'insertBefore' on 'Node'

Created on 26 Jan 2018  路  24Comments  路  Source: WebReflection/hyperHTML

I think I have a fundamental misunderstanding of how the library actually works so I don't think this is an issue with the library however below is my code and the error I get, I am not sure why this happens.

import { wire, bind } from "hyperhtml/esm";

window.wire = wire;
window.bind = bind;


var appRoot = document.getElementById("root");
var appRenderer = bind(appRoot);
// appRenderer is a function which takes 1 parameter: template
// I should call appRenderer with a template (``) everytime I want to update the UI.


function UI() {

    var timerInstance = timer(0, notify);
    var timerInstance2 = timer(0, notify);


    function render(){
        return  wire()`${timerInstance.render()}, ${timerInstance2.render()}`;
    };

    function notify(){
        console.log("I am notified!")
        subscriber(render());
    }

    var subscriber;

    return {
        subscribe: function (f) {
            subscriber = f;
        }
        , notify: function(){
            notify();
        }
    }
}

function timer(start, notify){
    var counter = start;
    var template = wire()`hi ${counter}`;

    setInterval(function(){
        counter++;
        template = wire()`hi ${counter}`
        notify();
    }, 2000)   

    return {
        render: function(){
            return template;
        }
    };
}

var ui = UI();
ui.subscribe(function (template) {
    appRenderer`${template};`
})
ui.notify();
I am notified!
bundle.js:956 Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
    at domdiff (http://localhost:9000/dist/bundle.js:956:20)
    at Array.anyContent (http://localhost:9000/dist/bundle.js:10491:99)
    at Array.update (http://localhost:9000/dist/bundle.js:855:16)
    at HTMLDivElement.render (http://localhost:9000/dist/bundle.js:828:12)
    at http://localhost:9000/dist/bundle.js:9968:16
    at notify (http://localhost:9000/dist/bundle.js:9934:9)
    at http://localhost:9000/dist/bundle.js:9956:9

All 24 comments

wire() creates a new DOM element/structure every single time

you are also using zero DOM in the whole example ... there's nothing that actually needs a DOM node.

window.wire = wire;
window.bind = bind;

if you use modules you don't need to export to the window anything, but I guess this was for testing purpose.

If you change the template value every single time, you are passing a new node. This is not really the way to go. If you want dynamic content you should use an array, so it can grow, reset, or change in the future.

var ui = UI();
ui.subscribe(function (template) {
    appRenderer`${[template]};`
})
ui.notify();

but that's not even it ... you are trashing every time everything instead of updating the only thing you care about: the number:

var appRenderer = bind(appRoot);

function UI() {

    var timerInstance = timer(0, notify);
    var timerInstance2 = timer(0, notify);


    function render(){
        return  wire(self)`${timerInstance.render()}, ${timerInstance2.render()}`;
    };

    function notify(){
        console.log("I am notified!")
        subscriber(render());
    }

    var subscriber;
    var self = {
        subscribe: function (f) {
            subscriber = f;
        }
        , notify: function(){
            notify();
        }
    };

    return self;
}

function timer(counter, notify){
    setInterval(function(){
        counter++;
        notify();
    }, 2000);

    return {
        render: function(){
            return wire(this)`hi ${counter}`;
        }
    };
}

var ui = UI();
ui.subscribe(function (template) {
    appRenderer`${template};`
})
ui.notify();

noe everything is fine because you update some DOM weakly referenced instead of trashing layouts and trash in new layout per each update.

Basically lit-html does the weakly reference internally but it cannot render anywhere else outside its place in the template literal while hyperHTML let you wire any reference and reuse its content updating only what's needed every single time.

You might also have found a weird edge case with all these new wires and I might have a look if something went wrong but for sure what you were doing was definitively not the way to use this library.

the quick summary:

var counter = 0;
function upate() {
  bind(element)`<div>I'm the element content with a ${
    wire(element || anyObject)`<strong>strong element in it ${counter++}</strong>`
  }</div>`;
}
setInterval(update, 1000);

You can always pre-address bound elements or wired objects as render and use them whenever it's convenient.

Be sure you read all examples to better understand the logic.

The example in the "quick summary" is what I see on examples, however I want my component to have internal state so I place variables inside the function.

Just to be sure, is the code below (after your modifications) does not intend what hyperHTML targets to provide, or I am completely lost :)

import { wire, bind } from "hyperhtml/esm";

// For debugging only.
window.wire = wire;
window.bind = bind;


var appRoot = document.getElementById("root");
var appRenderer = bind(appRoot);
// appRenderer is a function which takes 1 parameter: template
// I should call appRenderer with a template (``) everytime I want to update the UI.

function UI() {
    var subscriber;
    var timerInstance = timer(0, notify);
    var timerInstance2 = timer(100, notify);

    function render() {
        return wire() `<div><b>Timer 1:</b> ${timerInstance.render()}, <b>Timer 2:</b> ${timerInstance2.render()}</div>`;
    };

    function notify() {
        console.log("I am notified!")
        subscriber(render());
    }

    return {
        subscribe: function (f) {
            subscriber = f;
        }
        , notify: function () {
            notify();
        }
    }
}

function timer(counter, notify) {
    setInterval(function () {
        counter++;
        notify();
    }, 1000)

    return {
        render: function () {
            return wire() `hi ${counter}`;
        }
    };
}

var ui = UI();
ui.subscribe(function (template) {
    appRenderer`${[template]};`
})

as far as I understand, because the template is now an object having DOM elements inside (rather than a raw value), the library can compare and reflect the changes, rather than thinking it as a brand new value.

every time you wire() without passing a reference you lose the previous content and create new content from the scratch

can you see the difference ?

function UI() {
    var subscriber;
    var timerInstance = timer(0, notify);
    var timerInstance2 = timer(100, notify);
    var ui = {
      subscribe: function (f) {
        subscriber = f;
      },
      notify: function () {
        notify();
      }
    };

    return ui;

    function render() {
        return wire(ui)
          `<div>
            <b>Timer 1:</b> ${timerInstance.render()},
            <b>Timer 2:</b> ${timerInstance2.render()}
          </div>`;
    };

    function notify() {
        console.log("I am notified!")
        subscriber(render());
    }

}

you see wire(hasAReference) in my code? if you want to use it at runtime is important.

alternative:

function UI() {
    var subscriber;
    var timerInstance = timer(0, notify);
    var timerInstance2 = timer(100, notify);
    var render = wire();

    return {
      subscribe: function (f) {
        subscriber = f;
      },
      notify: function () {
        notify();
      }
    };

    function render() {
        return render
          `<div>
            <b>Timer 1:</b> ${timerInstance.render()},
            <b>Timer 2:</b> ${timerInstance2.render()}
          </div>`;
    };

    function notify() {
        console.log("I am notified!")
        subscriber(render());
    }

}

it's the same, as long as you don't invoke a wire without any argument to repeat the rendering of the exact same content.

This is the ABC of hyperHTML, once you get that, you'll have no other limitations to do anything.

also:

function timer(counter, notify) {
  var render = wire();
  setInterval(function () {
    counter++;
    notify();
  }, 1000)

  return {
    render: function () {
      return render`hi ${counter}`;
    }
  };
}

or, if you want to use the context as reference to never trash the wired DOM content:

function timer(counter, notify) {
  setInterval(function () {
    counter++;
    notify();
  }, 1000)

  return {
    render: function () {
      return wire(this)`hi ${counter}`;
    }
  };
}

say I have a Page having multiple Questions. I want to render each Question in the runtime. Hence, I will call the QuestionRendererComponent multiple times, iterating over the Questions array. Should I wire by passing the questionId as a reference?

I ask this to understand the intend behind the reference. If the reference is unique, then the library can find and update quickly, else it completely re-writes?

Should I wire by passing the questionId as a reference?

yes, or you'll trash same question content every time, including its state in terms of DOM tree/render/status

If the reference is unique, then the library can find and update quickly, else it completely re-writes?

you weakly relate some content to some data. that means hyperHTML directly update interpolations of the template. You can see that via debugger.

if you don't relate content to any data, you create new content every single time.

This is never what you want so either you scope const render = wire() and you use that render to return and update the related dom, you you can wire(data) and use it to update data.

Have a look at this little explanation of how things work here:
https://gist.github.com/WebReflection/d3aad260ac5007344a0731e797c8b1a4

bind or wire doesn't matter, wire has no parentNode, bind is the parentNode

Ok, I will work for a couple of hours to understand, by going over your links & codes. Thanks for your support

As far as I understand, even if people understand how wiring works and so they always use references for better performance, if they somehow forget passing a reference, nothing warn them. So they will think everything works fine even though there is a huge task going on behind (re-writing all nodes from scratch)

I understand your point, which looks like it is an advantage:

Basically lit-html does the weakly reference internally but it cannot render anywhere else outside its place in the template literal while hyperHTML let you wire any reference and reuse its content updating only what's needed every single time.

However, isn't it possible that if no reference is given, hyperHTML also weakly reference internally, like lit-html does? So that I can have both functionality. Normally I never care about references, the library takes care. If I want to be able to render it somewhere else, I pass a reference while wiring.

For instance when I've seen the example below, I had thought, "Why there is another clickHandler inside the wire(), even though the content already access it through closure". Now I understand that it is actually a hack to create a uniq ID. So, it can even be replaced by a random ID generator and it will still work?

function LoginButton(clickHandler) {
  return hyperHTML.wire(clickHandler)`
  <button onclick="${clickHandler}">
    Login
  </button>`;
}

there is a huge task going on behind

not so huge.

// the template `<li>list node ${INTERPOLATION}</li>`
// is encountered for the first time
const firstTime = wire()`<li>list node ${0}</li>`;

// that causes:
//  * some string manipulation
//  * creation of the template
//  * traversing the three to map interpolations to changes
//  * clone the template and map 1:1 updates for its nodes

// the next time the same TL is used though
const secondTime = wire()`<li>list node ${1}</li>`;

// the process is:
//  * clone the template and map 1:1 updates for its nodes

Accordingly, the cold start is the only expensive thing (but you can test all the benchmarks and see that even though it's not as expensive as it looks like).

So, if you don't wire the problem is not performance, the problem is Garbage Collector and all the states lost each time you meant to reuse the same DOM structure.

It's like keep using innerHTML (but faster than that in creation since there's no string parsing) over and over instead of updating the previous content ... which is the essential feature of both lit-html and hyperHTML. You never want to do that, unless you are creating nodes as one off, which leads to answer your next question ...

isn't it possible that if no reference is given, hyperHTML also weakly reference internally, like lit-html does?

lit-html does pretty much nothing until you pass its classes within its context.

// thingy is nothing useful in this world
const thingy = html`<div>Hello ${name}!</div>`

// until this happens
render(thingy, document.body);

the thingy is rendered, created, handled only once passed through the render. Basically the render does everything, and html tag provides hints.

Once some content is already inside the DOM, of course it's easy to relate the content to some reference ... guess which one lit-html uses? the parentNode.

This happens automatically in hyperHTML too when you bind something, which is somehow the equivalent of render in lit (except hyperHTML is a bit smarter because it recognizes HTML vs SVG)

// the content through the `<div>Hello ${name}!</div>`
// will always be the same node content
bind(node)`<div>Hello ${name}!</div>`;

Easy peasy lemon squeezy: there is a container used as weakly reference for a template.

However, wire does not have any container, hence it cannot weakly reference to anything.
The global object? No way to create two elements through the same template. A new {} each time? useless 'cause always different too.

But why? Doesn't the wire wait to be rendered through hyperHTML to be actually usable? No.

const me = wire()`<div>Hello ${'Andrea'}!</div>`;
const you = wire()`<div>Hello ${'Mustafa'}!</div>`;

// check this out
me instanceof HTMLDivElement;  // yup
you instanceof HTMLDivElement; // yup

// look mum, just DOM
document.body.appendChild(me);
document.body.appendChild(you);

As Summary

Through hyperHTML.wire(...) you can create some one-off container and use all the hyperHTML features you need.

If you need to reuse such container content,you either cache the wire or you reference an object.

// either cache a new wire once
const me = wire();
console.assert(
  me`<div>Hello ${'Andrea'}!</div>` ===
  me`<div>Hello ${'still Andrea'}!</div>`
); // true

// or pass a weak reference
const alsoMe = {name: 'Andrea'};
const render = o => wire(o)`<div>Hello ${o.name}!</div>` ;
console.assert(render(alsoMe) === render(alsoMe)); // true

If the same object is used to represent different containers, you can pass along a string id.

This concept is identical in every library with keyed renders, including React, Vue, etcetera.
lit-html does not have keyed renders out of the box so maybe this is what is confusing you.

Once you get this, you'll have unlocked all the potentials of the library. Until you forget that wires are like any template.cloneNode(true) if you don't relate them to anything, you'll keep trashing partial layouts for no reasons. Things will work until you realize you lose state per each update but ... nothing I can really do here, it's part of library features, not something to fix.

wire()`Regards`;

I forgot your last question:

So, it can even be replaced by a random ID generator and it will still work?

if it's a new ID every time you don't need it because you will create every time new content, which is again what you don't want.

just use any weak reference that makes sense to be sure the content is the same. Yes, even a callback, being just an object, can do the job. It's not a hack, it's a convenience in that example.

I thought I could go frameworkless however it seems like I definitely need one because even though hyperHTML is very cool, I think a framework will complement it better than I do :)

Do you think does that make sense ?
https://github.com/mustafaekim/hyperhtml-component

if that works for you, of course it makes sense :+1:

is there any framework that sits on hyperHTML, which will leverage my productivity? :)

hyper.Component is integrated and quite straight forward to use.

There's also HyperHTMLElement which is based on Custom Elements.

Together with attachShadow could give you a lot.

However, hypercomponent also is straight forward to use. Probably others such hyper-element or friends.

if you don't relate content to any data, you create new content every single time.
This is never what you want so either you scope const render = wire() and you use that render to return and update the related dom, you you can wire(data) and use it to update data.

how come:

const render = wire();
render`anydom`

does not trash the content but

wire()`anydom`

does?

one is a cached reference to a template literal tag function and another one create a new template literal tag function every single time?

const render = wire();
render`<p>hi</p>` === render`<p>hi</p>`; // true

// VS

wire()`<p>hi</p>` === wire()`<p>hi</p>`; // false, of course

Oh, I was thinking wire() always returns the same object, I dont know why I thought that way

function Welcome(props) {
  return hyperHTML.wire()`
  <h1>Hello, ${props.name}</h1>`;
}

So, all stateless components are trashing the content each time they are called?

function Welcome(props) {
  return hyperHTML.wire(props)`
  <h1>Hello, ${props.name}</h1>`;
}

btw ... this is not exactly how GitHub issues work ...

Was this page helpful?
0 / 5 - 0 ratings