Skip to content

Life-Cycle Hooks

Define behavior when certain events in the component’s life cycle is triggered by providing hook methods

See it live: Lifecycle order demo ↗ logs each hook as it fires, and Attribute lifecycle demo ↗ shows how attribute changes drive them.

  • Triggered when the component is connected to the DOM
  • Best for setting up the component
import { WebComponent } from 'https://esm.sh/web-component-base@latest'
class ClickableText extends WebComponent {
// gets called when the component is used in an HTML document
onInit() {
this.onclick = () => console.log('>>> click!')
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`
}
}
  • Triggered after the view is first initialized
class ClickableText extends WebComponent {
// gets called when the component's innerHTML is first filled
afterViewInit() {
const footer = this.querySelector('footer')
// do stuff to footer after view is initialized
}
get template() {
return `<footer>Awesome site &copy; 2023</footer>`
}
}
  • Triggered when the component is disconnected from the DOM
  • best for undoing any setup done in onInit()
import { WebComponent } from 'https://esm.sh/web-component-base@latest'
class ClickableText extends WebComponent {
clickCallback() {
console.log('>>> click!')
}
onInit() {
this.onclick = this.clickCallback
}
onDestroy() {
console.log('>>> removing event listener')
this.removeEventListener('click', this.clickCallback)
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`
}
}
  • Triggered when an attribute value changed
  • The changes object cleanly separates the property from the attribute:
    • property: the camelCase prop key, matching how you access props (e.g. myName)
    • attribute: the kebab-case attribute name that changed (e.g. my-name)
    • previousValue / currentValue: the values before and after the change

Use property to read the value straight off props (this.props[property]); use attribute when you need the raw attribute name.

See it live: onChanges payload demo ↗

import { WebComponent } from 'https://esm.sh/web-component-base@latest'
class ClickableText extends WebComponent {
// gets called when an attribute value changes
onChanges(changes) {
const { property, attribute, previousValue, currentValue } = changes
console.log('>>> ', { property, attribute, previousValue, currentValue })
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`
}
}

Upgrade ordering & the buffering guarantee

Section titled “Upgrade ordering & the buffering guarantee”

Per the Custom Elements spec, when an element is upgraded with attributes already present in the markup (e.g. <my-el my-name="Zoe">), the browser fires attributeChangedCallback before connectedCallback. Taken literally, that means render() and onChanges() could run before onInit(), so any setup you do in onInit (event wiring, reading external state) would not have happened yet on that first render. Test environments like happy-dom/jsdom don’t reproduce this ordering, so components can pass in tests and then misbehave in a real browser.

WebComponent removes this footgun. Attribute changes that arrive before the element is connected are buffered:

  • the prop value is applied immediately, so this.props is already correct inside onInit();
  • the render() and onChanges() side effects are deferred until after onInit() runs.

On connect, the order is always:

  1. onInit(): this.props already reflects any authored attributes
  2. a single render(): reflects all buffered props in one pass
  3. afterViewInit()

onChanges() never fires before onInit(). Pre-connect attribute changes are not replayed through onChanges(). The first render() already reflects them, so onChanges() is reserved for genuine post-connect changes. After the element is connected, attribute changes behave normally: each one triggers render() and onChanges() immediately.