Monday, September 11, 2023

LWC Component Lifecycle

 

 Component Lifecycle

Lightning web components have a lifecycle managed by the framework. The framework creates components, adds and removes them from the DOM, and renders DOM updates whenever the state of a component changes.

A lifecycle hook is a JavaScript callback method triggered at a specific phase of a component instance’s lifecycle.

 Lifecycle Flow

This diagram shows the flow of the component lifecycle from creation through render.

Shows the lifecycle of a component instance from creation through renderedCallback.

This is the lifecycle of a component instance and its children from creation through render.

  1. constructor() is called.
  2. Public properties are updated.
  3. The component is inserted into the DOM.
  4. connectedCallback() is called.
  5. A component is rendered.
  6. constructor() is called on child components.
  7. Public properties are updated on child components.
  8. Child components are inserted into the DOM.
  9. connectedCallback() is called on child components.
  10. Child components are rendered.
  11. renderedCallback() is called on child components.
  12. renderedCallback() is called on component.

This is the lifecycle of a component instance when it's removed from the DOM.

  1. A component is removed from the DOM.
  2. disconnectedCallback() is called on the component.
  3. Children are removed from the DOM.
  4. disconnectedCallback() is called on each child.

 constructor()

The constructor() method is invoked when a component instance is created.

The constructor flows from parent to child.

To access the host element, use this.template. You can’t access child elements in the component body because they don’t exist yet. Properties are not passed yet, either. Properties are assigned to the component after construction and before the connectedCallback() hook.

These requirements from the HTML: Custom elements spec apply to the constructor().

  • The first statement must be super() with no parameters. This call establishes the correct prototype chain and value for this. Always call super() before touching this.
  • Don’t use a return statement inside the constructor body, unless it is a simple early return (return or return this).
  • Don’t use the document.write() or document.open() methods.
  • Don’t inspect the element's attributes and children, because they don’t exist yet.
  • Don’t inspect the element’s public properties, because they’re set after the component is created.

Important

Don’t add attributes to the host element in the constructor. You can add attributes to the host element during any stage of the component lifecycle other than construction.

 connectedCallback()

The connectedCallback() lifecycle hook is invoked when a component is inserted into the DOM.

This hook flows from parent to child.

To access the host element, use this. You can’t access child elements in the component body because they don’t exist yet.

The connectedCallback() hook can fire more than once. For example, if you remove an element and then insert it into another position, such as when you reorder a list, the hook is invoked several times. If you want code to run one time, write code to prevent it from running twice.

You can use connectedCallback() to add an attribute to the host element.

import { LightningElement } from 'lwc'
export default class New extends LightningElement {
    connectedCallback() {
        this.classList.add('new-class');
    }
}

You can also use connectedCallback() and disconnectedCallback() to register and unregister event listeners. The browser garbage collects DOM events, so you can choose not to unregister them. However, relying on garbage collection can lead to memory leaks. See Memory Management on MDN.

Note

To check whether a component is connected to the DOM, you can use this.isConnected.

 disconnectedCallback()

The disconnectedCallback() lifecycle hook is invoked when a component is removed from the DOM.

This hook flows from parent to child.

 render()

For complex tasks like conditionally rendering a template, use render() to override the standard rendering functionality. This function may be invoked before or after connectedCallback().

This method must return a valid HTML template. Import a reference to a template and return the reference in the render() method.

See the code sample in Render Multiple Templates.

Note

The render() method is not technically a lifecycle hook. It is a protected method on the LightningElement class. A hook usually tells you that something happened, and it may or may not exist on the prototype chain. The render() method must exist on the prototype chain.

 renderedCallback()

Note

This lifecycle hook is specific to Lightning Web Components, it isn’t from the HTML custom elements specification.

Called after every render of the component. This hook flows from child to parent.

When a component re-renders, all the expressions used in the template are reevaluated.

Due to mutations, a component is usually rendered many times during the lifespan of an application. To use this hook to perform a one-time operation, use a boolean field like hasRendered to track whether renderedCallback() has been executed. The first time renderedCallback() executes, perform the one-time operation and set hasRendered = true. If hasRendered = true, don’t perform the operation.

It’s best to attach event listeners declaratively in the HTML template. However, if you want to attach an event listener to a template element programatically in JavaScript, use renderedCallback(). If a listener is added to the same element repeatedly, the browser removes the duplicates if the event type, event listener, and options are the same.

When a template is rerendered, the LWC engine attempts to reuse the existing elements. In the following cases, the engine uses a diffing algorithm to decide whether to discard an element.

  • Elements created using the for:each directive. The decision to reuse these iteration elements depends on the key attribute. If key changes, the element may be rerendered. If key doesn’t change, the element isn’t rerendered, because the engine assumes that it didn’t change.
  • Elements received as slot content. The engine attempts to reuse an element in a <slot>, but the diffing algorithm determines whether to evict an element and recreate it.

Example

The LibsChartjs recipe in the Lightning Web Components recipes app uses the renderedCallback() lifecycle hook.

/* libsChartjs.js */
import { LightningElement } from 'lwc';

const generateRandomNumber = () => {
    return Math.round(Math.random() * 100);
};

export default class LibsChartjs extends LightningElement {
    chart;
    chartjsInitialized = false;

    config = {
      // Code removed for brevity
    };

    renderedCallback() {
        if (this.chartjsInitialized) {
            return;
        }
        this.chartjsInitialized = true;
        this.loadChartJs();
    }

    async loadChartJs() {
        await require(/* webpackChunkName: "chartJs" */ 'chart.js');
        const ctx = this.template
            .querySelector('canvas.donut')
            .getContext('2d');
        this.chart = new window.Chart(ctx, this.config);
    }
}

 errorCallback(error, stack)

Note

This lifecycle hook is specific to Lightning Web Components, it isn’t from the HTML custom elements specification.

Called when a descendant component throws an error in one of its lifecycle hooks. The error argument is a JavaScript native error object, and the stack argument is a string.

Implement this hook to create an error boundary component that captures errors in all the descendant components in its tree. The error boundary component can log stack information and render an alternative view to tell users what happened and what to do next. The method works like a JavaScript catch{} block for components that throw errors in their lifecycle hooks. It’s important to note that an error boundary component catches errors only from its children, and not from itself.

You can create an error boundary component and reuse it throughout an app. It’s up to you where to define those error boundaries. You can wrap the entire app, or every individual component. Most likely, your architecture falls somewhere in between. A good rule of thumb is to think about where you’d like to tell users that something went wrong.

This example implements the errorCallback() method.

<!-- boundary.html -->
<template>
    <template lwc:if={error}>
        <error-view error={error} info={stack}></error-view>
    </template>
    <template lwc:else>
        <healthy-view></healthy-view>
    </template>
</template>
// boundary.js
import { LightningElement } from 'lwc';
export default class Boundary extends LightningElement {
    error;
    stack;
    errorCallback(error, stack) {
        this.error = error;
    }
}

You don’t have to use if:[true|false] in a template. For example, let’s say you define a single component template. If this component throws an error, the framework calls errorCallback and unmounts the component during re-render.

<!-- boundary.html -->
<template>
  <my-one-and-only-view></my-one-and-only-view>
</template>

Note

The errorCallback() hook doesn’t catch errors like click callbacks and async operations that fall outside of the component lifecycle (constructor()connectedCallback()render()renderedCallback()). It’s also important to note that errorCallback() isn’t a JavaScript try/catch, it’s a component lifecycle guard.