Search code examples
javascriptwebpackweb-componentcustom-element

Web Component "Cannot set property 'innerHTML' of null"


I have created a very basic custom element, that can change its value based on a provided attribute person. But whenever I'm loading my custom element I get this error: Cannot set property 'innerHTML' of null. When I add a breakpoint to the attributeChangedCallback function I can indeed see that on load the element is not there. When I continue loading though the element loads perfectly.

I could imagine because I'm using webpack to bundle all my files that the issue comes from loading the element at the end of the body instead of loading the element inside my head.

my-element.js:

class MyElement extends HTMLElement {
  constructor() {
     super();

     this.shadow = this.attachShadow({mode: 'open'});
     this._person = '';
  }

  get person() {
     return this._name;
  }

  set person(val) {
     this.setAttribute('person', val);
  }

  static get observedAttributes() {
     return ['person'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
     let myElementInner = this.shadow.querySelector('.my-element-inner');

     switch (attrName) {
        case 'person':
           this._person = newVal;

           // ======================
           // The error occures here
           // ======================
           myElementInner.innerHTML = `My name is ${this._person}`;

     }
  }

  connectedCallback() {
     var template =
     `
        <style>
        .my-element-inner {
           outline: blue dashed 1px;
           background-color: rgba(0,0,255,.1);
        }
        </style>
        <span class="my-element-inner">My name is ${this._person}</span>
     `

     this.shadow.innerHTML = template;
  }
}
customElements.define('my-element', MyElement);

index.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>WebPack Test Page</title>
</head>
<body>

  <my-element person="André"></my-element>

  <!-- Here goes the bundle.js -->
</body>
</html>

Solution

  • The attributeChangedCallback() can be called before or after the connectedCallback depending on how your custom element is used.

    If you move the connectedCallback logic to the constructor then things will be fine

    Another option would be to check if myElementInner is null and keep your code in the connectedCallback

    class MyElement extends HTMLElement {
      constructor() {
        super();
    
        this.shadow = this.attachShadow({mode: 'open'});
        this._person = '';
        var template =
          `
            <style>
            .my-element-inner {
               outline: blue dashed 1px;
               background-color: rgba(0,0,255,.1);
            }
            </style>
            <span class="my-element-inner">My name is ${this._person}</span>
         `
    
        this.shadow.innerHTML = template;
      }
    
      get person() {
        return this._person;
      }
    
      set person(val) {
        this.setAttribute('person', val);
      }
    
      static get observedAttributes() {
        return ['person'];
      }
    
      attributeChangedCallback(attrName, oldVal, newVal) {
        let myElementInner = this.shadow.querySelector('.my-element-inner');
    
        switch (attrName) {
          case 'person':
            this._person = newVal;
            if (myElementInner) {
              myElementInner.innerHTML = `My name is ${this._person}`;
            }
    
        }
      }
    }
    customElements.define('my-element', MyElement);
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
      <title>WebPack Test Page</title>
    </head>
    
    <body>
    
      <my-element person="André"></my-element>
    
      <!-- Here goes the bundle.js -->
    </body>
    
    </html>