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.querySelector or lwc: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 detail property
  • Use bubbles: true and composed: true when 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: true and composed: true for 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;
        });
}

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/await instead of Promises when possible
  • Always handle errors with try/catch
  • Set loading states appropriately
  • Use Promise.all for parallel operations
  • Clean up async operations in disconnectedCallback if 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