Introduction
Lightning Web Components (LWC) is Salesforce’s modern framework for building custom user interfaces in Salesforce applications. Understanding the component lifecycle is crucial for efficient component development, optimizing performance, and implementing complex functionalities.
What are Lifecycle Hooks?
Lifecycle hooks are special methods that get executed at specific points during a component’s existence. They allow developers to run custom code at key moments such as when a component is created, rendered, updated, or removed from the DOM. By leveraging these hooks strategically, you can control component behavior throughout its lifecycle.
The LWC Component Lifecycle
A Lightning Web Component goes through the following phases:
- Creation: The component is constructed and initialized
- Rendering: The component’s HTML template is rendered to the DOM
- DOM Insertion: The component is inserted into the DOM
- Updates: The component’s state changes, causing re-renders
- Removal: The component is removed from the DOM

Let’s explore each lifecycle hook in detail.
Constructor
When it runs: At component creation, before any other lifecycle hook.
Use cases:
- Initialize component properties
- Set initial state
- Bind event methods
Do not:
- Access DOM elements
- Call external services
- Access child components
Example:
import { LightningElement } from 'lwc';
export default class LifecycleDemo extends LightningElement {
constructor() {
super(); // Always call super() first
// Initialize properties
this.counter = 0;
this.initialized = true;
// Bind methods to maintain 'this' context
this.handleClick = this.handleClick.bind(this);
console.log('Constructor executed');
}
}
connectedCallback
When it runs: When a component is inserted into the DOM.
Use cases:
- Fetch data from an Apex controller
- Subscribe to events
- Set up timers or intervals
- Initialize third-party libraries
Example:
import { LightningElement } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';
export default class LifecycleDemo extends LightningElement {
contacts;
error;
connectedCallback() {
console.log('Component connected to DOM');
// Fetch data when component is inserted into DOM
getContacts()
.then(result => {
this.contacts = result;
this.error = undefined;
})
.catch(error => {
this.error = error;
this.contacts = undefined;
});
// Set up interval
this.intervalId = setInterval(() => {
this.counter++;
}, 1000);
// Subscribe to platform events
this.subscription = subscribe(
this.channelName,
-1,
this.handleEvent.bind(this)
);
}
}
renderedCallback
When it runs: After every render of the component. It runs after the first render and after every re-render caused by state changes.
Use cases:
- Access or manipulate DOM elements
- Integrate with third-party libraries that need DOM access
- Perform calculations based on rendered component dimensions
- Adjust component layout after rendering
Example:
import { LightningElement } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import chartjs from '@salesforce/resourceUrl/chartjs';
export default class ChartComponent extends LightningElement {
chart;
chartInitialized = false;
renderedCallback() {
console.log('Component rendered/re-rendered');
// Only initialize the chart library once
if (this.chartInitialized) {
return;
}
this.chartInitialized = true;
// Load the ChartJS library
loadScript(this, chartjs)
.then(() => {
// Get the canvas element
const canvas = this.template.querySelector('canvas');
// Initialize chart
this.chart = new Chart(canvas, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
label: 'Colors',
data: [12, 19, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)'
],
borderWidth: 1
}]
}
});
})
.catch(error => {
console.error('Error loading ChartJS', error);
});
}
}
disconnectedCallback
When it runs: When a component is removed from the DOM.
Use cases:
- Clean up resources
- Cancel pending operations
- Unsubscribe from events
- Clear timers or intervals
- Release memory
Example:
import { LightningElement } from 'lwc';
import { unsubscribe } from 'lightning/empApi';
export default class LifecycleDemo extends LightningElement {
subscription;
intervalId;
connectedCallback() {
// Set up interval
this.intervalId = setInterval(() => {
console.log('Interval running');
}, 1000);
// Subscribe to platform events
this.subscription = subscribe(
this.channelName,
-1,
this.handleEvent.bind(this)
);
}
disconnectedCallback() {
console.log('Component removed from DOM');
// Clear interval
if (this.intervalId) {
clearInterval(this.intervalId);
}
// Unsubscribe from events
if (this.subscription) {
unsubscribe(this.subscription);
}
// Clean up any other resources
if (this.chart) {
this.chart.destroy();
}
}
}
errorCallback
errorCallback(error, stack)
- When it runs: Only when an unhandled error occurs in a child component
- Purpose: Provides a way for parent components to handle errors from their child components
- Parameters:
error: The JavaScript error objectstack: The stack trace string
The errorCallback does not catch errors that occur:
- In the component itself where the
errorCallbackis defined - In event handlers of the component
- In the component’s own lifecycle hooks
- In the component’s
render()method
Example:
<!-- parentComponent.html -->
<template>
<div class="parent-container">
<h2>Parent Component</h2>
<!-- Conditionally display error message -->
<template if:true={showError}>
<div class="error-message">
<lightning-icon icon-name="utility:error" alternative-text="Error" variant="error"></lightning-icon>
<span>{errorMessage}</span>
</div>
</template>
<!-- Child component that might throw an error -->
<c-error-prone-child></c-error-prone-child>
<div class="container">
<p>The parent component can catch and handle errors from the child component above.</p>
</div>
</div>
</template>
// parentComponent.js
import { LightningElement } from 'lwc';
export default class ParentComponent extends LightningElement {
// errorCallback lifecycle hook
// This runs when a child component throws an error
errorCallback(error, stack) {
// Handle error from child component
console.error('Error in child component:', error.message);
console.log('Stack trace:', stack);
// Implement custom error handling
// For example, display a friendly message to the user
this.errorMessage = 'Something went wrong in a child component. Please try again later.';
// You could also report the error to a logging service
// this.logErrorToService(error, stack);
// Return true to indicate that you've handled the error
return true;
}
// Example method to log errors to a service
logErrorToService(error, stack) {
// Implementation for error logging
}
}
/* parentComponent.css */
.parent-container {
padding: 1rem;
background-color: #f3f3f3;
border-radius: 4px;
}
.error-message {
background-color: #ffdde1;
color: #c23934;
padding: 0.5rem;
border-radius: 4px;
margin: 1rem 0;
display: flex;
align-items: center;
}
.error-message lightning-icon {
margin-right: 0.5rem;
}
.container {
margin-top: 1rem;
}
Child Component
// errorProneChild.js
import { LightningElement, api } from 'lwc';
export default class ErrorProneChild extends LightningElement {
@api
get throwError() {
return this._throwError;
}
set throwError(value) {
this._throwError = value;
if (value === true) {
this.causeError();
}
}
_throwError = false;
connectedCallback() {
// This will intentionally cause an error after the component is connected
// In a real scenario, errors might happen due to data issues, unexpected state, etc.
setTimeout(() => {
this.causeError();
}, 2000);
}
causeError() {
// This will cause an error that will be caught by the parent's errorCallback
const nonExistentObject = undefined;
try {
// This will throw a TypeError
nonExistentObject.someProperty = 'This will cause an error';
} catch (error) {
// Throwing the error from here will trigger the parent's errorCallback
throw new Error('Child component failed: ' + error.message);
}
}
handleClick() {
// Another way to trigger an error
this.causeError();
}
}
<!-- errorProneChild.html -->
<template>
<div class="child-container">
<h3>Error-Prone Child Component</h3>
<p>This component will throw an error shortly after loading, which the parent will catch.</p>
<lightning-button
label="Force Error"
variant="destructive"
onclick={handleClick}>
</lightning-button>
</div>
</template>
/* errorProneChild.css */
.child-container {
padding: 1rem;
background-color: white;
border: 1px solid #d8dde6;
border-radius: 4px;
margin-top: 1rem;
}
render()
When it runs: Before each time a component renders or re-renders.
Use cases:
- Conditionally specify which template to render
- Implement dynamic templates based on state
- Swap templates based on device type or form factor
Example:
import { LightningElement } from 'lwc';
import desktopTemplate from './desktopView.html';
import mobileTemplate from './mobileView.html';
import tabletTemplate from './tabletView.html';
export default class ResponsiveComponent extends LightningElement {
formFactor = 'DESKTOP';
// Determine device type on component initialization
connectedCallback() {
// This is simplified - you'd use more robust detection in a real app
const width = window.innerWidth;
if (width < 768) {
this.formFactor = 'PHONE';
} else if (width < 1024) {
this.formFactor = 'TABLET';
} else {
this.formFactor = 'DESKTOP';
}
}
// Dynamically return the appropriate template
render() {
console.log('Render method called, form factor:', this.formFactor);
switch(this.formFactor) {
case 'PHONE':
return mobileTemplate;
case 'TABLET':
return tabletTemplate;
default:
return desktopTemplate;
}
}
}
Lifecycle Flow and Best Practices
Order of Execution
For a typical component, lifecycle hooks are executed in this order:
constructor()render()(initial)connectedCallback()renderedCallback()(first render)
When a component’s state changes:
render()renderedCallback()
When a component is removed:
disconnectedCallback()
Best Practices
- Keep the constructor lightweight
- Avoid complex operations
- Only initialize properties
- Fetch data in connectedCallback
- Not in constructor
- Not in renderedCallback (would create infinite loops)
- Interact with DOM in renderedCallback
- Never in constructor or connectedCallback
- Add guards to prevent repeated executions when not needed
- Use errorCallback for robust error handling
- Always provide user-friendly error messages
- Log detailed errors for debugging
- Always clean up in disconnectedCallback
- Cancel subscriptions
- Clear timers
- Release resources
Troubleshooting Common Issues
Infinite Rendering Loops
A common issue is creating infinite rendering loops. This happens when you modify reactive properties in renderedCallback() without proper guards.
Problem:
renderedCallback() {
// This creates an infinite loop!
this.counter++;
}
Solution:
renderedCallback() {
if (this.firstRender) {
this.firstRender = false;
this.counter++;
}
}
Accessing DOM Elements Too Early
Problem:
connectedCallback() {
// This will fail - DOM isn't ready yet
const div = this.template.querySelector('div');
div.focus();
}
Solution:
renderedCallback() {
// Now the DOM is ready
const div = this.template.querySelector('div');
div.focus();
}
Advanced Patterns with Lifecycle Hooks
Component Communication with Lifecycle
// Parent component
import { LightningElement } from 'lwc';
export default class ParentComponent extends LightningElement {
childReady = false;
handleChildReady() {
this.childReady = true;
console.log('Child component is ready');
}
}
// Child component
import { LightningElement } from 'lwc';
export default class ChildComponent extends LightningElement {
connectedCallback() {
// Notify parent that child is ready
setTimeout(() => {
const readyEvent = new CustomEvent('childready');
this.dispatchEvent(readyEvent);
}, 0);
}
}
Conditional Rendering and Lifecycle
import { LightningElement, track } from 'lwc';
export default class ConditionalComponent extends LightningElement {
@track showComponent = false;
toggleComponent() {
this.showComponent = !this.showComponent;
}
// This will run whenever component is shown or hidden
renderedCallback() {
console.log('Component render state changed:', this.showComponent ? 'showing' : 'hidden');
}
}
Conclusion
Understanding and effectively using Lightning Web Component lifecycle hooks is essential for building robust, performant Salesforce applications. By applying the right hook at the right time, you can create components that initialize properly, interact seamlessly with external systems, clean up after themselves, and handle errors gracefully.
Remember the key principles:
- Use the constructor for basic initialization
- Use connectedCallback for setup and data fetching
- Use renderedCallback for DOM interactions
- Use disconnectedCallback for cleanup
- Use errorCallback for error handling
- Use render() for dynamic templates

One thought on “Salesforce LWC Lifecycle Hooks Explained”
Comments are closed.