LWC Lifecycle and Events
Lifecycle Hooks
Follow lifecycle hooks in execution order: connectedCallback -> renderedCallback -> disconnectedCallback, with errorCallback as a cross-cutting safety hook. See Salesforce lifecycle guidance for connectedCallback, renderedCallback, disconnectedCallback, and errorCallback.
connectedCallback
Usage: Use connectedCallback to initialize the component when it's inserted into the DOM.
Guidelines:
- Initialize component state
- Set up event listeners (if needed)
- Subscribe to message channels
- Don't perform heavy operations - use wire adapters or async methods instead
Examples:
export default class MyComponent extends LightningElement {
_connected = false;
connectedCallback() {
this._connected = true;
this.classList.add('my-component');
this.initializeComponent();
}
initializeComponent() {
// Light initialization
this.setupEventListeners();
}
setupEventListeners() {
// Set up event listeners if needed
}
}
renderedCallback
Usage: Use renderedCallback to perform DOM manipulation after the component renders.
Guidelines:
- Use sparingly - prefer declarative templates when possible
- Check if work has already been done to avoid infinite loops
- Access DOM elements using
this.template.querySelectororlwc:ref - Don't modify component properties that trigger re-rendering
Examples:
export default class MyComponent extends LightningElement {
_initialized = false;
renderedCallback() {
// Check if already initialized to avoid infinite loops
if (this._initialized) {
return;
}
const button = this.template.querySelector('button');
if (button) {
// Perform DOM manipulation
button.classList.add('custom-class');
this._initialized = true;
}
}
}
Common Use Cases:
- Setting focus on elements
- Initializing third-party libraries
- Measuring DOM elements
- Setting CSS custom properties dynamically
disconnectedCallback
Usage: Use disconnectedCallback to clean up when the component is removed from the DOM.
Guidelines:
- Unsubscribe from message channels
- Remove event listeners
- Clean up timers/intervals
- Cancel pending promises/requests if possible
Examples:
export default class MyComponent extends LightningElement {
subscription;
intervalId;
connectedCallback() {
this.subscribeToMessages();
this.startInterval();
}
disconnectedCallback() {
// Unsubscribe from messages
if (this.subscription) {
unsubscribe(this.subscription);
}
// Clear interval
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
errorCallback
Usage: Use errorCallback to handle errors thrown during rendering, lifecycle hooks, or event handlers.
Guidelines:
- Log errors appropriately
- Provide user feedback
- Prevent error propagation if possible
- Set error state for UI
Examples:
export default class MyComponent extends LightningElement {
error;
errorCallback(error, stack) {
// Log error
console.error('Component error:', error);
console.error('Stack trace:', stack);
// Set error state
this.error = error;
// Show user-friendly error message
this.showErrorToast('An error occurred. Please try again.');
}
showErrorToast(title, message) {
// Show toast notification
}
}
Lifecycle Hook Best Practices
- Keep lifecycle hooks focused and lightweight
- Avoid heavy operations in
connectedCallback - Use flags to prevent infinite loops in
renderedCallback - Always clean up in
disconnectedCallback - Handle errors appropriately in
errorCallback
Event Handling
Use this order when designing events: name the event contract, dispatch from child, then handle in parent.
Event Naming Conventions
Guidelines:
- Use lowercase event names (no spaces)
- Be descriptive about what triggered the event
- Use verb + past tense for action events:
clicked,changed,selected - Use noun + verb for state events:
loadstarted,loadcompleted
Examples:
// Good: Action events
'actionclicked'
'buttonclicked'
'itemselected'
// Good: State events
'loadstarted'
'loadcompleted'
'validationfailed'
// Good: Change events
'valuechanged'
'selectionchanged'
'filterchanged'
Custom Events
Usage: Use CustomEvent to communicate from child to parent components.
Guidelines:
- Use descriptive event names
- Include relevant data in
detailproperty - Use
bubbles: trueandcomposed: truewhen parent components must receive the event across shadow boundaries - Follow event naming conventions
Examples:
// Good: Dispatch custom event with data
handleClick() {
const event = new CustomEvent('actionclicked', {
detail: {
recordId: this.recordId,
action: 'clicked',
timestamp: Date.now()
},
bubbles: true,
composed: true
});
this.dispatchEvent(event);
}
// Good: Simple event without data
handleChange() {
this.dispatchEvent(new CustomEvent('change', {
bubbles: true,
composed: true
}));
}
Parent Component Usage:
<!-- Parent component template -->
<template>
<c-child-component onactionclicked={handleActionClicked}></c-child-component>
</template>
// Parent component JavaScript
handleActionClicked(event) {
const { recordId, action } = event.detail;
// Handle event
}
Event Handler Patterns
Naming:
- Prefix event handlers with
handle:handleClick,handleChange,handleSubmit - Be descriptive about what the handler does
Examples:
// Good: Descriptive handler names
handleClick() {
// Handle click
}
handleInputChange(event) {
const value = event.target.value;
this.value = value;
}
handleFormSubmit(event) {
event.preventDefault();
// Handle form submission
}
handleItemSelect(event) {
const selectedItem = event.detail.item;
this.selectedItem = selectedItem;
}
Standard Events
Usage: Use standard DOM events when appropriate (click, change, input, etc.) and create custom events only when you need to publish component-specific state upward.
Examples:
// Good: Handle standard click event directly
handleClick(event) {
// Handle click
}
// Good: Normalize input then emit custom valuechanged event
handleInputChange(event) {
const value = event.target.value;
this.dispatchEvent(new CustomEvent('valuechanged', {
detail: { value },
bubbles: true,
composed: true
}));
}
Event Best Practices
- Always include relevant data in event
detail - Use
bubbles: trueandcomposed: truefor custom events that must cross component boundaries - Document event names and payload structure
- Handle events appropriately in parent components
- Use standard DOM events when possible
Async Patterns
Start with async/await as the default, use raw Promise chains only when necessary, and apply cleanup/guards for async work that may outlive the component.
Async/Await
Usage: Use async/await for cleaner asynchronous code (preferred over Promises).
Examples:
// Good: Use async/await
async handleSubmit() {
try {
this.isLoading = true;
const result = await saveRecord(this.recordData);
this.showToast('Success', 'Record saved successfully', 'success');
this.isLoading = false;
} catch (error) {
this.handleError(error);
this.isLoading = false;
}
}
// Good: Multiple async operations
async loadAllData() {
try {
const [accounts, contacts] = await Promise.all([
getAccounts(),
getContacts()
]);
this.accounts = accounts;
this.contacts = contacts;
} catch (error) {
this.handleError(error);
}
}
// Good: Basic async operation
async loadData() {
try {
this.isLoading = true;
const data = await fetchData();
this.data = data;
this.isLoading = false;
} catch (error) {
this.handleError(error);
this.isLoading = false;
}
}
Promises
Usage: Use Promises for asynchronous operations when async/await is not available or appropriate.
Note: async/await is preferred over Promises for better readability and error handling. Use Promises only when necessary.
Examples:
// Acceptable: Handle promise with then/catch (async/await preferred)
loadData() {
return fetchData()
.then((data) => {
this.data = data;
this.isLoading = false;
})
.catch((error) => {
this.handleError(error);
this.isLoading = false;
});
}
Navigation with Async
Usage: Handle NavigationMixin promises appropriately.
Examples:
async handleNavigate() {
try {
await this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
});
} catch (error) {
this.showToast('Error', 'Failed to navigate', 'error');
}
}
async handleGenerateUrl() {
try {
const url = await this[NavigationMixin.GenerateUrl]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
});
window.open(url, '_blank');
} catch (error) {
this.showToast('Error', 'Failed to generate URL', 'error');
}
}
Async Best Practices
- Use
async/awaitinstead of Promises when possible - Always handle errors with try/catch
- Set loading states appropriately
- Use
Promise.allfor parallel operations - Clean up async operations in
disconnectedCallbackif needed
Change History
Version 1.0 - 2026-03-26
- Added initial version history tracking for this document.
Last Updated: 2026-03-26 Version: 1.0