Lifecycle

Lit components use the standard custom element lifecycle methods. In addition Lit introduces a reactive update cycle that renders changes to DOM when reactive properties change.

Lit components are standard custom elements and inherit the custom element lifecycle methods. For information about the custom element lifecycle, see Using the lifecycle callbacks on MDN.

If you need to customize any of the standard custom element lifecycle methods, make sure to call the super implementation (such as super.connectedCallback()) so the standard Lit functionality is maintained.

Called when an element is created. Also, it’s invoked when an existing element is upgraded, which happens when the definition for a custom element is loaded after the element is already in the DOM.

Lit behavior

Requests an asynchronous update using the requestUpdate() method, so when a Lit component gets upgraded, it performs an update immediately.

Saves any properties already set on the element. This ensures values set before upgrade are maintained and correctly override defaults set by the component.

Use cases

Perform one time initialization tasks that must be done before the first update. For example, when not using decorators, default values for properties can be set in the constructor, as shown in Declaring properties in a static properties field.

Invoked when a component is added to the document's DOM.

Lit behavior

Lit initiates the first element update cycle after the element is connected. In preparation for rendering, Lit also ensures the renderRoot (typically, its shadowRoot) is created.

Once an element has connected to the document at least once, component updates will proceed regardless of the connection state of the element.

Use cases

In connectedCallback() you should setup tasks that should only occur when the element is connected to the document. The most common of these is adding event listeners to nodes external to the element, like a keydown event handler added to the window. Typically, anything done in connectedCallback() should be undone when the element is disconnected — for example, removing event listeners on window to prevent memory leaks.

Invoked when a component is removed from the document's DOM.

Lit behavior

Pauses the reactive update cycle. It is resumed when the element is connected.

Use cases

This callback is the main signal to the element that it may no longer be used; as such, disconnectedCallback() should ensure that nothing is holding a reference to the element (such as event listeners added to nodes external to the element), so that it is free to be garbage collected. Because elements may be re-connected after being disconnected, as in the case of an element moving in the DOM or caching, any such references or listeners may need to be re-established via connectedCallback() so that the element continues functioning as expected in these scenarios. For example, remove event listeners to nodes external to the element, like a keydown event handler added to the window.

No need to remove internal event listeners. You don't need to remove event listeners added on the component's own DOM—including those added declaratively in your template. Unlike external event listeners, these won't prevent the component from being garbage collected.

Invoked when one of the element’s observedAttributes changes.

Lit behavior

Lit uses this callback to sync changes in attributes to reactive properties. Specifically, when an attribute is set, the corresponding property is set. Lit also automatically sets up the element’s observedAttributes array to match the component’s list of reactive properties.

Use cases

You rarely need to implement this callback.

Invoked when a component is moved to a new document.

Be aware that adoptedCallback is not polyfilled.

Lit behavior

Lit has no default behavior for this callback.

Use cases

This callback should only be used for advanced use cases when the element behavior should change when it changes documents.

In addition to the standard custom element lifecycle, Lit components also implement a reactive update cycle.

The reactive update cycle is triggered when a reactive property changes or when the requestUpdate() method is explicitly called. Lit performs updates asynchronously so property changes are batched — if more properties change after an update is requested, but before the update starts, all of the changes are captured in the same update.

Updates happen at microtask timing, which means they occur before the browser paints the next frame to the screen. See Jake Archibald's article on microtasks for more information about browser timing.

At a high level, the reactive update cycle is:

  1. An update is scheduled when one or more properties change or when requestUpdate() is called.
  2. The update is performed prior to the next frame being painted.
    1. Reflecting attributes are set.
    2. The component’s render method is called to update its internal DOM.
  3. The update is completed and the updateComplete promise is resolved.

In more detail, it looks like this:

Pre-Update

Update

Post-Update

An update is triggered when a reactive property changes or the requestUpdate() method is called. Since updates are performed asynchronously, any and all changes that occur before the update is performed result in only a single update.

hasChanged()

Called when a reactive property is set. By default hasChanged() does a strict equality check and if it returns true, an update is scheduled. See configuring hasChanged() for more information.

requestUpdate()

Call requestUpdate() to schedule an explicit update. This can be useful if you need the element to update and render when something not related to a property changes. For example, a timer component might call requestUpdate() every second.

The list of properties that have changed is stored in a Map that’s passed to all the subsequent lifecycle methods. The Map keys are the property names and its values are the previous property values.

Optionally, you can pass a property name and a previous value when calling requestUpdate(), which will be stored in the changedProperties map. This can be useful if you implement a custom getter and setter for a property. See Reactive properties for more information about implementing custom getters and setters.

When an update is performed, the performUpdate() method is called. This method calls a number of other lifecycle methods.

Any changes that would normally trigger an update which occur while a component is updating do not schedule a new update. This is done so that property values can be computed during the update process.

shouldUpdate()

Called to determine whether an update cycle is required.

ArgumentschangedProperties: Map with keys that are the names of changed properties and values that are the corresponding previous values.
UpdatesNo. Property changes inside this method do not trigger an element update.
Call super?Not necessary.
Called on server?No.

If shouldUpdate() returns true, which it does by default, then the update proceeds normally. If it returns false, the rest of the update cycle will not be called but the updateComplete Promise is still resolved.

You can implement shouldUpdate() to specify which property changes should cause updates. Use the map of changedProperties to compare current and previous values.

willUpdate()

Called before update() to compute values needed during the update.

ArgumentschangedProperties: Map with keys that are the names of changed properties and values that are the corresponding previous values.
Updates?No. Property changes inside this method do not trigger an element update.
Call super?Not necessary.
Called on server?Yes.

Implement willUpdate() to compute property values that depend on other properties and are used in the rest of the update process.

update()

Called to update the component's DOM.

ArgumentschangedProperties: Map with keys that are the names of changed properties and values that are the corresponding previous values.
Updates?No. Property changes inside this method do not trigger an element update.
Call super?Yes. Without a super call, the element’s attributes and template will not update.
Called on server?No.

Reflects property values to attributes and calls render() to update the component’s internal DOM.

Generally, you should not need to implement this method.

render()

Called by update() and should be implemented to return a renderable result (such as a TemplateResult) used to render the component's DOM.

ArgumentsNone.
Updates?No. Property changes inside this method do not trigger an element update.
Call super?Not necessary.
Called on server?Yes.

The render() method has no arguments, but typically it references component properties. See Rendering for more information.

After update() is called to render changes to the component's DOM, you can perform actions on the component's DOM using these methods.

firstUpdated()

Called after the component's DOM has been updated the first time, immediately before updated() is called.

ArgumentschangedProperties: Map with keys that are the names of changed properties and values that are the corresponding previous values.
Updates?Yes. Property changes inside this method schedule a new update cycle.
Call super?Not necessary.
Called on server?No.

Implement firstUpdated() to perform one-time work after the component's DOM has been created. Some examples might include focusing a particular rendered element or adding a ResizeObserver or IntersectionObserver to an element.

updated()

Called whenever the component’s update finishes and the element's DOM has been updated and rendered.

ArgumentschangedProperties: Map with keys that are the names of changed properties and values that are the corresponding previous values.
Updates?Yes. Property changes inside this method trigger an element update.
Call super?Not necessary.
Called on server?No.

Implement updated() to perform tasks that use element DOM after an update. For example, code that performs animation may need to measure the element DOM.

updateComplete

The updateComplete Promise resolves when the element has finished updating. Use updateComplete to wait for an update. The resolved value is a Boolean indicating if the element has finished updating. It will be true if there are no pending updates after the update cycle has finished.

It is a good practice to dispatch events from components after rendering has completed, so that the event's listeners see the fully rendered state of the component. To do so, you can await the updateComplete Promise before firing the event.

Also, when writing tests you can await the updateComplete Promise before making assertions about the component’s DOM.

performUpdate()

Implements the reactive update cycle, calling the other methods, like shouldUpdate(), update(), and updated().

Call performUpdate() to immediately process a pending update. This should generally not be needed, but it can be done in rare cases when you need to update synchronously.

Implement performUpdate() to customize the timing of the update cycle. This can be useful for implementing custom scheduling. Note, if performUpdate() returns a Promise, the updateComplete Promise will await it.

In this example, the update is performed after paint. This technique can be used to unblock the main rendering/event thread. See the Chrome Dev Summit talk by Justin Fagnani The Virtue of Laziness for an extended discussion.

hasUpdated

The hasUpdated property returns true if the component has updated at least once. You can use hasUpdated in any of the lifecycle methods to perform work only if the component has not yet updated.

getUpdateComplete()

To await additional conditions before fulfilling the updateComplete promise, override the getUpdateComplete() method. For example, it may be useful to await the update of a child element. First await super.getUpdateComplete(), then any subsequent state.

It's recommended to override the getUpdateComplete() method instead of the updateComplete getter to ensure compatibility with users who are using TypeScript's ES5 output (see TypeScript#338).

External lifecycle hooks: controllers and decorators

Permalink to “External lifecycle hooks: controllers and decorators”

In addition to component classes implementing lifecycle callbacks, external code, such as decorators may need to hook into a component's lifecycle.

Lit offers two concepts for external code to integrate with the reactive update lifecycle: static addInitializer() and addController():

static addInitializer()

addInitializer() allows code that has access to a Lit class definition to run code when instances of the class are constructed.

This is very useful when writing custom decorators. Decorators are run at class definition time, and can do things like replace field and method definitions. If they also need to do work when an instance is created, they must call addInitializer(). It will be common to use this to add a reactive controller so decorators can hook into the component lifecycle:

Decorating a field will then cause each instance to run an initializer that adds a controller:

Initializers are stored per-constructor. Adding an initializer to a subclass does not add it to a superclass. Since initializers are run in constructors, initializers will run in order of the class hierarchy, starting with superclasses and progressing to the instance's class.

addController()

addController() adds a reactive controller to a Lit component so that the component invokes the controller's lifecycle callbacks. See the Reactive Controller docs for more information.

removeController()

removeController() removes a reactive controller so it no longer receives lifecycle callbacks from this component.

Lit’s server-side rendering code is currently in an experimental stage so the following information is subject to change.

Not all of the update cycle is called when rendering Lit on the server. The following methods are called on the server.