Understanding LWC lifecycle hooks helps you manage initialization and DOM interactions safely. This post focuses on connectedCallback, renderedCallback, and related lifecycle stages.
The LWC lifecycle follows this order:
Called when the component is first created. Use it for initializing properties.
import { LightningElement, track } from 'lwc';
export default class MyComponent extends LightningElement {
@track value = '';
@track loading = false;
constructor() {
super();
// Initialize properties
this.value = 'Default Value';
this.loading = false;
console.log('Constructor called');
}
}
Called when the component is inserted into the DOM. This is where you initialize data, set up listeners, and fetch data.
import { LightningElement, track, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
export default class MyComponent extends LightningElement {
@track accounts;
listener;
connectedCallback() {
console.log('connectedCallback called');
// Initialize data
this.loadAccounts();
// Set up event listeners
window.addEventListener('scroll', this.handleScroll);
// Subscribe to events
this.subscribeToMessages();
}
loadAccounts() {
// Fetch data
getAccounts()
.then(data => {
this.accounts = data;
})
.catch(error => {
console.error('Error loading accounts:', error);
});
}
handleScroll() {
console.log('Page scrolled');
}
subscribeToMessages() {
// Subscribe to message channel
}
}
Called after the component finishes rendering. Use it for DOM manipulation and accessing DOM elements.
export default class MyComponent extends LightningElement {
@track data = [];
renderedCallback() {
console.log('renderedCallback called');
// Access DOM elements
const element = this.template.querySelector('.my-element');
if (element) {
element.classList.add('active');
}
// Initialize third-party libraries
this.initializeChart();
// Set focus
const input = this.template.querySelector('input');
if (input) {
input.focus();
}
}
initializeChart() {
// Initialize Chart.js or similar library
const canvas = this.template.querySelector('canvas');
if (canvas) {
// Chart initialization code
}
}
}
export default class SafeComponent extends LightningElement {
@track initialized = false;
renderedCallback() {
// Guard against multiple calls
if (this.initialized) {
return;
}
this.initialized = true;
// Safe DOM manipulation
this.setupDOM();
}
setupDOM() {
const button = this.template.querySelector('button');
if (button) {
button.addEventListener('click', this.handleClick);
}
}
}
Called when the component is removed from the DOM. Clean up resources and listeners.
export default class MyComponent extends LightningElement {
scrollHandler = this.handleScroll.bind(this);
connectedCallback() {
// Add listener
window.addEventListener('scroll', this.scrollHandler);
}
disconnectedCallback() {
console.log('disconnectedCallback called');
// Remove event listeners
window.removeEventListener('scroll', this.scrollHandler);
// Unsubscribe from message channels
this.unsubscribeFromMessages();
// Cancel pending requests
this.cancelPendingRequests();
// Clear timers
if (this.timer) {
clearTimeout(this.timer);
}
}
unsubscribeFromMessages() {
// Cleanup subscription
}
cancelPendingRequests() {
// Cancel any pending API calls
}
}
import { LightningElement, track, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
export default class DataTableComponent extends LightningElement {
@track accounts = [];
@track loading = false;
@track error;
pageSize = 10;
currentPage = 1;
sortField = 'Name';
constructor() {
super();
console.log('[1] Constructor');
}
connectedCallback() {
console.log('[2] connectedCallback');
this.loadAccounts();
}
async loadAccounts() {
this.loading = true;
try {
const data = await getAccounts();
this.accounts = data;
this.error = undefined;
} catch (error) {
this.error = error.body.message;
} finally {
this.loading = false;
}
}
renderedCallback() {
console.log('[3] renderedCallback');
// Setup sorting
const headers = this.template.querySelectorAll('th');
headers.forEach(header => {
header.addEventListener('click', (e) => {
this.sortField = e.target.dataset.field;
// Re-sort data
});
});
}
disconnectedCallback() {
console.log('[4] disconnectedCallback');
// Clean up
}
}
Parent updates to @api properties trigger lifecycle hooks in child:
// Child component
export default class ChildComponent extends LightningElement {
@api recordId;
@track record;
connectedCallback() {
if (this.recordId) {
this.loadRecord();
}
}
loadRecord() {
// Fetch record data
}
}
// Parent component
export default class ParentComponent extends LightningElement {
@track selectedId = '001';
handleSelect(event) {
this.selectedId = event.target.value;
// Child's renderedCallback fires after parent updates dom
}
}
| Lifecycle Hook | When Called | Can Access DOM? | Can Update State? |
|---|---|---|---|
| constructor | Instantiation | ❌ No | ✅ Yes |
| connectedCallback | Inserted to DOM | ❌ Limited | ✅ Yes |
| renderedCallback | After rendering | ✅ Yes | ❌ No (risk of loops) |
| disconnectedCallback | Removed from DOM | ❌ No | ❌ No |