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.
Standard custom element lifecycle
Permalink to “Standard custom element lifecycle”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.
constructor()
Permalink to “constructor()”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
Permalink to “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
Permalink to “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.
connectedCallback()
Permalink to “connectedCallback()”Invoked when a component is added to the document's DOM.
Lit behavior
Permalink to “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
Permalink to “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.
disconnectedCallback()
Permalink to “disconnectedCallback()”Invoked when a component is removed from the document's DOM.
Lit behavior
Permalink to “Lit behavior”Pauses the reactive update cycle. It is resumed when the element is connected.
Use cases
Permalink to “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.
attributeChangedCallback()
Permalink to “attributeChangedCallback()”Invoked when one of the element’s observedAttributes
changes.
Lit behavior
Permalink to “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
Permalink to “Use cases”You rarely need to implement this callback.
adoptedCallback()
Permalink to “adoptedCallback()”Invoked when a component is moved to a new document.
Be aware that adoptedCallback
is not polyfilled.
Lit behavior
Permalink to “Lit behavior”Lit has no default behavior for this callback.
Use cases
Permalink to “Use cases”This callback should only be used for advanced use cases when the element behavior should change when it changes documents.
Reactive update cycle
Permalink to “Reactive update cycle”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:
- An update is scheduled when one or more properties change or when
requestUpdate()
is called. - The update is performed prior to the next frame being painted.
- Reflecting attributes are set.
- The component’s render method is called to update its internal DOM.
- The update is completed and the
updateComplete
promise is resolved.
In more detail, it looks like this:
Pre-Update
Update
Post-Update
The changedProperties map
Permalink to “The changedProperties map”Many reactive update methods receive a Map
of changed properties. The Map
keys are the property names and its values are the previous property values. You can always find the current property values using this.property
or this[property]
.
TypeScript types for changedProperties
Permalink to “TypeScript types for changedProperties”If you're using TypeScript and you want strong type checking for the changedProperties
map, you can use PropertyValues<this>
, which infers the correct type for each property name.
If you're less concerned with strong typing—or you're only checking the property names, not the previous values—you could use a less restrictive type like Map<string, any>
.
Note that PropertyValues<this>
doesn't recognize protected
or private
properties. If you're checking any protected
or private
properties, you'll need to use a less restrictive type.
Changing properties during an update
Permalink to “Changing properties during an update”Changing a property during the update (up to and including the render()
method) updates the changedProperties
map, but doesn't trigger a new update. Changing a property after render()
(for example, in the updated()
method) triggers a new update cycle, and the changed property is added to a new changedProperties
map to be used for the next cycle.
Triggering an update
Permalink to “Triggering an 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()
Permalink to “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()
Permalink to “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 changedProperties
map that’s passed to 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.
Performing an update
Permalink to “Performing an update”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. Properties changed during the update are reflected in the changedProperties
map, so subsequent lifecycle methods can act on the changes.
shouldUpdate()
Permalink to “shouldUpdate()”Called to determine whether an update cycle is required.
Arguments | changedProperties : 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? | 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()
Permalink to “willUpdate()”Called before update()
to compute values needed during the update.
Arguments | changedProperties : 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()
Permalink to “update()”Called to update the component's DOM.
Arguments | changedProperties : 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()
Permalink to “render()”Called by update()
and should be implemented to return a renderable result (such as a TemplateResult
) used to render the component's DOM.
Arguments | None. |
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.
Completing an update
Permalink to “Completing an update”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()
Permalink to “firstUpdated()”Called after the component's DOM has been updated the first time, immediately before updated()
is called.
Arguments | changedProperties : 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()
Permalink to “updated()”Called whenever the component’s update finishes and the element's DOM has been updated and rendered.
Arguments | changedProperties : 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
Permalink to “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.
When an element updates, it may cause its children to update as well. By default, the updateComplete
promise resolves when the element's update has completed, but does not wait for any children to have completed their updates. This behavior may be customized by overriding getUpdateComplete
.
There are several use cases for needing to know when an element's update has completed:
Tests When writing tests you can await the
updateComplete
promise before making assertions about a component’s DOM. If the assertions depend on updates completing for the component's entire descendant tree, awaitingrequestAnimationFrame
is often a better choice, since Lit's default scheduling uses the browser's microtask queue, which is emptied prior to animation frames. This ensures all pending Lit updates on the page have completed before therequestAnimationFrame
callback.Measurement Some components may need to measure DOM in order to implement certain layouts. While it is always better to implement layouts using pure CSS rather than JavaScript-based measurement, sometimes CSS limitations make this unavoidable. In very simple cases, and if you're measuring Lit or ReactiveElement components, it may be sufficient to await
updateComplete
after state changes and before measuring. However, becauseupdateComplete
does not await the update of all descendants, we recommend usingResizeObserver
as a more robust way to trigger measurement code when layouts change.Events 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.
The updateComplete
promise rejects if there's an unhandled error during the update cycle. For more information, see Handling errors in the update cycle.
Handling errors in the update cycle
Permalink to “Handling errors in the update cycle”If you have an uncaught exception in a lifecycle method like render()
or update()
, it causes the updateComplete
promise to reject. If you have code in a lifecycle method that can throw an exception, it's good practice to put it inside a try
/catch
statement.
You may also want to use a try
/catch
if you're awaiting the updateComplete
promise:
In some cases, code may throw in unexpected places. As a fallback, you can add a handler for window.onunhandledrejection
to catch these issues. For example, you could use this report errors back to a backend service to help diagnose issues that are hard to reproduce.
Implementing additional customization
Permalink to “Implementing additional customization”This section covers some less-common methods for customizing the update cycle.
scheduleUpdate()
Permalink to “scheduleUpdate()”Override scheduleUpdate()
to customize the timing of the update. scheduleUpdate()
is called when an update is about to be performed, and by default it calls performUpdate()
immediately. Override it to defer the update—this technique can be used to unblock the main rendering/event thread.
For example, the following code schedules the update to occur after the next frame paints, which can reduce jank if the update is expensive:
If you override scheduleUpdate()
, it's your responsibility to call super.scheduleUpdate()
to perform the pending update.
Async function optional.
This example shows an async function which implicitly returns a promise. You can also write scheduleUpdate()
as a function that explictly returns a Promise
. In either case, the next update doesn't start until the promise returned by scheduleUpdate()
resolves.
performUpdate()
Permalink to “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. (If there is no update pending, you can call requestUpdate()
followed by performUpdate()
to force a synchronous update.)
Use scheduleUpdate()
to customize scheduling.
If you want to customize how updates are scheduled, override scheduleUpdate()
. Previously, we recommended overriding performUpdate()
for this purpose. That continues to work, but it makes it more difficult to call performUpdate()
to process a pending update synchronously.
hasUpdated
Permalink to “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()
Permalink to “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()
Permalink to “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()
Permalink to “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()
Permalink to “removeController()”removeController()
removes a reactive controller so it no longer receives lifecycle callbacks from this component.
Server-side reactive update cycle
Permalink to “Server-side reactive update cycle”Lit's server-side rendering package is currently under active development 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.