Lightning Web Component (LWC) Patterns

LWC Fundamentals

Component Structure

Component bundle:

Component lifecycle:

Best practice: Understand component lifecycle hooks. Use lifecycle hooks appropriately. Avoid heavy operations in constructor. Use connectedCallback for initialization.

Decorators and Properties

@api: Public properties and methods exposed to parent components.

@track: Reactive properties that trigger re-render when changed (deprecated in modern LWC, properties are reactive by default).

@wire: Reactive service that automatically provides data or calls methods.

@AuraEnabled: Methods callable from Aura components or external contexts.

Best practice: Use @api for public properties. Use @wire for reactive data access. Properties are reactive by default (no need for @track in modern LWC).

Data Access Patterns

Lightning Data Service (LDS):

Wire adapters:

Imperative Apex calls:

Best practice: Use LDS wire adapters for reactive data access. Use imperative Apex calls for on-demand actions. Handle errors appropriately.

Event Handling

Component events: Communication between child and parent components.

Lightning Message Service: Communication between components that aren’t in parent-child relationship.

Standard events: Platform events like recordUpdated, recordCreated.

Best practice: Use component events for parent-child communication. Use Lightning Message Service for cross-component communication. Handle events appropriately.

When to Use LWCs

LWCs are surgical tools for complex UI needs when:

Component Patterns

Console-Style LWCs

Purpose: Agent consoles for case workers, advisors, internal staff

Features:

Fraud/Risk Scoring LWC

Purpose: Display fraud or risk scores for clients/cases

Features:

Implementation Pattern:

Program-Selection LWC

Purpose: Let applicants or staff select academic programs with real eligibility rules

Features:

Implementation Pattern:

Advisor Management LWC

Purpose: Staff management of advisor information

Features:

Implementation Pattern:

Reusable LWC Patterns

Service-Layer Pattern

Pattern: Apex classes expose clean methods for LWCs

Config-Driven UI

Pattern: Use custom metadata / custom settings to drive:

Benefits: Environment-specific configuration without code changes

Dynamic Field Display Patterns

Pattern: Dynamically show fields in lightning-record-edit-form based on Custom Metadata configuration

When to use: When field visibility needs to be configurable without code changes, or when different user types need different field sets.

Implementation approach:

Why it’s recommended: Instead of hardcoding field names in LWC components, storing field APIs in Custom Metadata allows administrators to configure which fields appear without code changes. This enables environment-specific configurations and easier maintenance.

Example scenario: A record edit form that needs to show different fields based on the record type. Field configurations are stored in Custom Metadata, fetched by Apex, and dynamically rendered in the LWC component.

Implementation Pattern:

Step 1: Custom Metadata Type Structure

Create a Custom Metadata Type Field_Configuration__mdt with fields:

Step 2: Apex Method to Fetch Field Configurations

public with sharing class FieldConfigurationService {
    
    /**
     * Gets field configurations for an object and optional record type
     * @param objectApiName Object API name
     * @param recordTypeId Optional record type ID
     * @return List of field API names in display order
     */
    @AuraEnabled(cacheable=true)
    public static List<String> getFieldConfigurations(String objectApiName, String recordTypeId) {
        List<String> fieldApiNames = new List<String>();
        
        // Query Custom Metadata
        List<Field_Configuration__mdt> configs = [
            SELECT Field_API_Name__c, Display_Order__c
            FROM Field_Configuration__mdt
            WHERE Object_API_Name__c = :objectApiName
            AND Is_Active__c = true
            ORDER BY Display_Order__c ASC
        ];
        
        // Extract field API names
        for (Field_Configuration__mdt config : configs) {
            fieldApiNames.add(config.Field_API_Name__c);
        }
        
        return fieldApiNames;
    }
}

Step 3: LWC Component with Dynamic Fields

import { LightningElement, wire, api } from 'lwc';
import { getFieldConfigurations } from 'c/fieldConfigurationService';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class DynamicRecordEditForm extends LightningElement {
    @api recordId;
    @api objectApiName;
    
    fieldApiNames = [];
    isLoading = true;
    
    @wire(getFieldConfigurations, { 
        objectApiName: '$objectApiName',
        recordTypeId: null 
    })
    wiredFieldConfigurations({ data, error }) {
        if (data) {
            this.fieldApiNames = data;
            this.isLoading = false;
        } else if (error) {
            this.showError('Error loading field configuration', error);
            this.isLoading = false;
        }
    }
    
    handleSuccess(event) {
        this.showSuccess('Record saved successfully');
        // Optionally refresh or navigate
    }
    
    handleError(event) {
        this.showError('Error saving record', event.detail);
    }
    
    showSuccess(message) {
        this.dispatchEvent(new ShowToastEvent({
            title: 'Success',
            message: message,
            variant: 'success'
        }));
    }
    
    showError(title, message) {
        this.dispatchEvent(new ShowToastEvent({
            title: title,
            message: message,
            variant: 'error'
        }));
    }
}

Step 4: HTML Template with Dynamic Fields

<template>
    <lightning-card title="Edit Record">
        <template if:true={isLoading}>
            <lightning-spinner alternative-text="Loading"></lightning-spinner>
        </template>
        
        <template if:false={isLoading}>
            <lightning-record-edit-form
                record-id={recordId}
                object-api-name={objectApiName}
                onsuccess={handleSuccess}
                onerror={handleError}>
                
                <template for:each={fieldApiNames} for:item="fieldApiName">
                    <lightning-input-field
                        key={fieldApiName}
                        field-name={fieldApiName}>
                    </lightning-input-field>
                </template>
                
                <lightning-button
                    class="slds-m-top_small"
                    type="submit"
                    label="Save">
                </lightning-button>
            </lightning-record-edit-form>
        </template>
    </lightning-card>
</template>

Key Points:

Benefits:

Related Patterns: Order of Execution - Understanding when LWCs execute

Related Domains:

Q&A

Q: When should I use LWC instead of standard page layouts or Flows?

A: Use LWC when you need to combine data from multiple objects or systems, require complex client-side decisions and filtering, need responsive interactive experiences, or standard layouts and Flows cannot handle the requirement. LWCs are surgical tools for complex UI needs that declarative tools cannot address.

Q: What is the difference between @wire and imperative Apex calls?

A: @wire is reactive and automatically provides data when parameters change. Use @wire for data that should update automatically. Imperative Apex calls (methodName({ params })) are on-demand and give you control over when to fetch data. Use imperative calls for actions, updates, or when you need to control the timing of data access.

Q: How do I handle errors in LWC components?

A: Wrap Apex calls in try-catch blocks. Display user-friendly error messages using lightning-messages or custom error components. Log errors to a logging service for troubleshooting. Provide recovery paths (e.g., retry buttons) when appropriate. Always handle loading states to show users when operations are in progress.

Q: How do I optimize LWC performance?

A: Minimize server round trips by batching data requests into single Apex methods. Use cacheable methods with cache busting when fresh data is needed. Implement lazy loading for detailed breakdowns that aren’t needed immediately. Defer heavy calculations to async jobs if needed. Use refreshApex carefully to avoid unnecessary server calls.

Q: How do I make LWC components accessible?

A: Follow WCAG 2.2 guidelines. Use semantic HTML elements. Provide proper labels for all form inputs. Ensure keyboard navigation works for all interactive elements. Maintain proper focus indicators. Ensure sufficient color contrast. Test with screen readers. Use ARIA attributes when semantic HTML isn’t sufficient.

Q: Can I use LWC in Experience Cloud (Community) portals?

A: Yes, LWCs work in Experience Cloud portals. However, you need to ensure proper security and sharing. Use with sharing in Apex classes called from LWCs. Consider portal user permissions and sharing sets. Test thoroughly with different portal user types to ensure proper access control.

Q: How do I pass data between parent and child LWC components?

A: Use @api properties to pass data from parent to child. Use component events to communicate from child to parent. Use Lightning Message Service for communication between components that aren’t in a parent-child relationship. Use @track is not needed in modern LWC (properties are reactive by default).

Q: How do I update records in LWC?

A: Use updateRecord from Lightning Data Service for standard objects when possible. Use imperative Apex calls with @AuraEnabled methods for custom objects or complex updates. Always handle errors and provide user feedback. Use refreshApex to update wire adapter data after updates if needed.

Q: What is the best way to handle large data sets in LWC?

A: Implement pagination or infinite scroll for large lists. Use lazy loading to defer loading detailed data until needed. Consider server-side filtering and sorting to reduce data transfer. Use Platform Cache for frequently accessed data. Consider using Platform Events for real-time updates instead of polling.

Q: How do I test LWC components?

A: Use Jest for unit testing LWC components. Test component rendering, user interactions, and data flow. Mock Apex methods and wire adapters. Test error handling scenarios. Use @salesforce/sfdx-lwc-jest for Salesforce-specific testing utilities. Aim for high code coverage, especially for business logic.