LitElement connectedCallback vs. firstUpdated

Recently, as I was trying to fix an issue with Emblem Snake, I learned some more about the lifecycle methods in LitElement and thought I would share what I learned.

Problem: connectedCallback called before first DOM update on iOS Safari

Emblem Snake uses multiple images overlayed on top of each other with CSS scaling and transforms. Since this is the web, and the user can resize their browser however they’d like, I also needed to update my component whenever the window size changed.

LitElement by default updates whenever you assign a new value to a property. So, I created two properties to maintain the current width and height of my element:

@internalProperty()
width = 0;
@internalProperty()
height = 0;

I can then use these values to update my UI whenever the screen size changes.

import debounce from 'lodash-es/debounce';
...
connectedCallback() {
  ...
  window.addEventListener(
    'resize',
    debounce(() => this.updateWidthAndHeight(), 100)
  );
  ...
}
...
private updateWidthAndHeight() {
  const { width, height } = this.getBoundingClientRect();
  this.width = Math.floor(width);
  this.height = Math.floor(height);
}

Notice that I’m setting the resize event listener in the connectedCallback. This callback is invoked every time a custom element is appended to the DOM. This means that when your browser needs to render a new custom element, this callback is invoked after it is constructed.

Now, the problem is that my width and height properties are initialized to zero. I need to get the initial width and height of my element. Naturally my first guess was to just put it inside connectedCallback.

connectedCallback() {
  ...
  this.updateWithAndHeight();
  window.addEventListener(
    'resize',
    debounce(() => this.updateWidthAndHeight(), 100)
  );
  ...
}

This pretty much worked 99% of the time. But, for some reason it didn’t work on iOS Safari. After using the Safari Web Inspector, I found to my surprise that the initial width and height were still set to zero!

After reading more about connectedCallback I theorized that there must not be a guarantee that it is called after the DOM is rendered. If it’s called before, then getBoundingClientRect will obviously return a zero width and height.

Solution: use firstUpdated instead

The firstUpdated callback is guaranteed to be invoked after the first DOM update. This means that getBoundingClientRect will return non-zero values since the DOM has actually been rendered.