Lightning Web Components (LWC) provide a powerful and performant way to build dynamic and interactive Salesforce applications. But when dealing with multi-layered related data, performance, maintainability, and user experience become critical factors. A well-optimized LWC controller should:

  • Efficiently retrieve and display multiple levels of related data (e.g., Accounts → Opportunities → Line Items).
  • Dynamically refresh data when updates occur, ensuring real-time consistency.
  • Use toast messages for clear user feedback on actions (success, error, or warnings).

In this post, we’ll walk through some best practices to build a high-performing LWC controller that dynamically loads related data, refreshes efficiently, and improves user experience with toast messages.


Define the Use Case & Data Model

Scenario:

We’re building an LWC that displays Accounts and their related Opportunities, which further contain Line Items. Users can update data at any level, and the component should automatically refresh without a full page reload.

Data Structure:

  • Account (Parent)
    • Opportunities (Child)
      • Opportunity Line Items (Grandchild)

Our goal is to efficiently fetch, update, and refresh this hierarchical data structure in LWC.


Query Data Efficiently Using Apex

Fetching multi-layered related data in a single Apex call reduces network requests and improves performance. Here’s how to optimize the SOQL query:

Apex Controller (AccountController.cls)

public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccountsWithOpportunities() {
        return [SELECT Id, Name, (SELECT Id, Name, Amount, StageName, 
                                  (SELECT Id, Quantity, UnitPrice, TotalPrice, Product2.Name 
                                   FROM OpportunityLineItems) 
                                  FROM Opportunities) 
                FROM Account 
                WHERE Id IN (SELECT AccountId FROM Opportunity)];
    }

    @AuraEnabled
    public static void updateOpportunity(Opportunity opp) {
        update opp;
    }
}

Key Optimizations:

  • Single SOQL Query: Retrieves Accounts → Opportunities → Line Items in one request, reducing API calls.
  • @AuraEnabled(cacheable=true): Enables client-side caching, reducing server load.
  • WHERE Condition: Restricts data to Accounts that actually have Opportunities, improving efficiency.

Build the LWC Component

Component Structure:

  • accountList.html → Renders the hierarchy (Accounts → Opportunities → Line Items).
  • accountList.js → Calls Apex methods, refreshes data, and handles UI interactions.
  • accountList.js-meta.xml → Defines metadata for Lightning App Builder usage.

LWC Markup (accountList.html)

<template>
    <lightning-card title="Accounts and Opportunities">
        <template if:true={accounts}>
            <template for:each={accounts} for:item="account">
                <lightning-accordion key={account.Id}>
                    <lightning-accordion-section name={account.Id} label={account.Name}>
                        <template for:each={account.Opportunities} for:item="opp">
                            <div key={opp.Id} class="slds-m-around_medium">
                                <lightning-tile label={opp.Name}>
                                    <p>Stage: {opp.StageName}</p>
                                    <p>Amount: {opp.Amount}</p>
                                    <lightning-button 
                                        label="Update Stage"
                                        data-id={opp.Id}
                                        onclick={handleUpdateOpportunity}>
                                    </lightning-button>
                                    <template if:true={opp.OpportunityLineItems}>
                                        <lightning-accordion>
                                            <template for:each={opp.OpportunityLineItems} for:item="lineItem">
                                                <lightning-accordion-section key={lineItem.Id} label={lineItem.Product2.Name}>
                                                    <p>Quantity: {lineItem.Quantity}</p>
                                                    <p>Price: {lineItem.UnitPrice}</p>
                                                    <p>Total: {lineItem.TotalPrice}</p>
                                                </lightning-accordion-section>
                                            </template>
                                        </lightning-accordion>
                                    </template>
                                </lightning-tile>
                            </div>
                        </template>
                    </lightning-accordion-section>
                </lightning-accordion>
            </template>
        </template>
        <template if:false={accounts}>
            <p class="slds-text-color_error">No records found.</p>
        </template>
    </lightning-card>
</template>

Key UI Features:

  • Accordion Layout: Helps users easily navigate through related records.
  • Dynamic Data Rendering: Uses for:each loops to iterate through nested data.
  • Inline Updates: Includes a button to update Opportunity records.

LWC JavaScript Controller (accountList.js)

import { LightningElement, wire, track } from 'lwc';
import getAccountsWithOpportunities from '@salesforce/apex/AccountController.getAccountsWithOpportunities';
import updateOpportunity from '@salesforce/apex/AccountController.updateOpportunity';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';

export default class AccountList extends LightningElement {
    @track accounts;
    wiredAccounts;

    @wire(getAccountsWithOpportunities)
    wiredAccountsHandler(result) {
        this.wiredAccounts = result;
        if (result.data) {
            this.accounts = result.data;
        } else if (result.error) {
            this.showToast('Error', 'Failed to load data', 'error');
        }
    }

    handleUpdateOpportunity(event) {
        const oppId = event.target.dataset.id;
        const updatedOpportunity = { Id: oppId, StageName: 'Closed Won' };

        updateOpportunity({ opp: updatedOpportunity })
            .then(() => {
                this.showToast('Success', 'Opportunity updated successfully', 'success');
                return refreshApex(this.wiredAccounts);
            })
            .catch(error => {
                this.showToast('Error', 'Update failed', 'error');
            });
    }

    showToast(title, message, variant) {
        this.dispatchEvent(new ShowToastEvent({
            title,
            message,
            variant
        }));
    }
}

Key Functionalities:

  • @wire with Refreshable Data: Caches the Apex response and dynamically refreshes using refreshApex().
  • Toast Messages: Uses ShowToastEvent to display feedback messages when updates occur.
  • Inline Data Updates: Updates an Opportunity’s stage and auto-refreshes data to reflect changes.

Deploy & Test

Deployment Steps:

  1. Deploy the Apex Controller (AccountController.cls) to your Salesforce org.
  2. Deploy the LWC (accountList) to Salesforce.
  3. Add the LWC component to a Lightning Record Page or App Page.

Testing Scenarios:

  • Verify that Accounts, Opportunities, and Line Items load correctly.
  • Update an Opportunity’s stage and confirm the toast message appears.
  • Ensure the data refreshes automatically after an update.
  • Check that error handling works by testing with insufficient permissions.

Final Thoughts

In this post we went a bit deeper into the guts of designing and creating a LWC for Salesforce.com; including a few insights into best practice design:

A well-architected LWC controller enhances performance, user experience, and maintainability. By:

  • Using optimized Apex queries to fetch multi-layered data in one call.
  • Implementing dynamic refreshes with refreshApex() to keep data up to date.
  • Providing toast messages to improve user feedback.

You ensure that your LWC not only functions efficiently but also delivers a seamless and interactive user experience.


Other Blog Links:


We would love to hear your comments!

Trending