LWC Lifecycle Hooks – connectedCallback & renderedCallback

Understanding LWC lifecycle hooks helps you manage initialization and DOM interactions safely. This post focuses on connectedCallback, renderedCallback, and related lifecycle stages.

1. Component Lifecycle Stages

The LWC lifecycle follows this order:

  1. constructor() – Component instance created
  2. connectedCallback() – Component inserted into DOM
  3. renderedCallback() – Component rendered to DOM
  4. disconnectedCallback() – Component removed from DOM

2. Constructor

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');
    }
}
  

⚠️ Constructor Limitations:

3. connectedCallback()

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
    }
}
  

4. renderedCallback()

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
        }
    }
}
  

⚠️ renderedCallback Warnings:


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);
        }
    }
}
  

5. disconnectedCallback()

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
    }
}
  

6. Lifecycle Examples in Action

Example: Data Table Component


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
    }
}
  

7. Lifecycle with @api Properties

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
    }
}
  

8. Lifecycle Hooks Timing

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

Key Takeaways