HyperHTML - is it possible to update a component state without re rendering the entire component DOM?

563 Views Asked by At

I have the following hyperHTML component, everything is working as intended, the only issue is that the entire component DOM re renders on every this.setState() call.

My question:

Is there a way to update the notification string on the DOM without re rendering the entire component DOM ?

const { hyper } = hyperHTML;

class searchComponent extends hyper.Component {
    constructor(data) {
        super();
        this.setState({ notification: '' });
    }
    onChange() {
        // ...
        if ( condition ) {
            this.setState(() => ({ notification: 'notification text!' }));
        }
        // ...
    }
    async listTags() {
        //...
        // async content 
        //...
    }
    render() {
        this.html `
        <div id="search_container">
            <select name="tags" id="tags_search" class='form-control m0' multiple="multiple" onChange=${this.onChange}>
            ${{
                any: this.listTags(),
                placeholder: 'incoming..!',
            }}
            </select>
            <div class="search_notification">
                <!-- I want to re render only this part of the DOM -->
                ${this.state.notification}
            </div>
        </div>
        `
    }
}
1

There are 1 best solutions below

1
On BEST ANSWER

There are few gotchas in your code. To start with, that onChange won't work as expected, you are losing the context as it is. You either just use onchange HTML event, and you call the method onchange and you use simply this as event listener:

const { hyper } = hyperHTML;

class SearchComponent extends hyper.Component {
  onchange() {
    this instaneof SearchComponent; // true
  }
  render() { return this.html`
    <div id="search_container">
      <select onchange=${this}>
      ${...}
      </select>
    </div>`
  }
}

or you bind the onChange method within the constructor.

You also forgot to return this.html inside render(), which is something needed if you want to place your component on the DOM.

However, assuming these were irrelevant errors there for quick demo code sake, you need to understand that async world is async.

If you have an asynchronous operation there's no way you can tell if that resolved or not, you just pass it along, and this is exactly what you are doing.

You are changing the state of the component expecting that previously async operations would not be triggered again.

But how can the component know if such state change would produce a different layout? The TL;DR answer is that it cannot, the slightly longer one is that you have an asynchronous, unrelated, behavior, attached to each updates but you want that asynchronous behavior to be triggered only once, and not per update.

Possible solutions

You can either create a sub component a part, and instantiate it once during the SearchComponent initialization (constructor) or simply use a placeholder that makes more sense than the current one.

Indeed, right now you are creating output like <select>incoming..!</select> which makes no sense as standard HTML.

A select can have option tags, using it as a generic container of some text is like abusing its potentials.

What you want is something like a disabled=${this.optionsResolved} on the select, and ${this.options} as array of its content.

In this way you have no changes whatsoever while resolving your options, and a proper list of options once that happens.

You can see an example in this Code Pen