Skip to main content

How to Chain Reactive Wire Adapters in Salesforce LWC: Step-by-Step Guide

In plain words: Chaining wire methods in Lightning Web Components (LWC) means making one wire adapter automatically wait for and react to the output of another by prefixing the parameter name with a dollar sign ('$propertyName'). When the first method delivers data, the dependent wire triggers automatically.

In enterprise Salesforce development, related data models frequently require sequential fetching—such as loading an Account, extracting its primary Contact, and then loading all related Opportunities. Using LWC's reactive wire parameters allows you to chain these calls declaratively without nested callback pyramids or imperative promise chains.

Prerequisites

  • An active Salesforce Developer Edition, Scratch Org, or Sandbox.
  • Salesforce CLI (sf) configured in your local environment.
  • Familiarity with reactive JavaScript properties and @wire service decorators.

Step 1: Create the Server-Side Apex Controller

To enable wire service communication, all Apex methods must be marked with @AuraEnabled(cacheable=true). The controller below provides three interdependent query methods:

public with sharing class ChainWireController {
    
    @AuraEnabled(cacheable=true)
    public static List<Account> getTopAccount() {
        return [
            SELECT Id, Name 
            FROM Account 
            WITH USER_MODE 
            ORDER BY CreatedDate DESC 
            LIMIT 1
        ];
    }

    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactsByAccountId(Id accountId) {
        if (accountId == null) {
            return new List<Contact>();
        }
        return [
            SELECT Id, Name, Email 
            FROM Contact 
            WHERE AccountId = :accountId 
            WITH USER_MODE 
            LIMIT 5
        ];
    }

    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpportunitiesByAccountId(Id accountId) {
        if (accountId == null) {
            return new List<Opportunity>();
        }
        return [
            SELECT Id, Name, StageName, Amount 
            FROM Opportunity 
            WHERE AccountId = :accountId 
            WITH USER_MODE 
            LIMIT 5
        ];
    }
}
Warning Trap: A wire method will not execute if any of its dynamic reactive parameters (prefixed with $) resolve to undefined. However, if the value evaluates to null, the method executes with null. Always add null-checks inside your Apex classes to prevent unexpected SOQL exceptions.

Step 2: Implement Reactive Wire Chaining in JavaScript

In the component controller, wire the root data first. Once the initial payload populates a reactive property (selectedAccountId), the dependent wire services trigger automatically:

import { LightningElement, wire } from 'lwc';
import getTopAccount from '@salesforce/apex/ChainWireController.getTopAccount';
import getContactsByAccountId from '@salesforce/apex/ChainWireController.getContactsByAccountId';
import getOpportunitiesByAccountId from '@salesforce/apex/ChainWireController.getOpportunitiesByAccountId';

export default class ChainWireDemo extends LightningElement {
    selectedAccountId;
    accounts;
    contacts;
    opportunities;

    // Step 1: Initial wire to fetch the top account
    @wire(getTopAccount)
    wiredAccount({ data, error }) {
        if (data && data.length > 0) {
            this.accounts = data;
            // Setting this triggers the chained wires below
            this.selectedAccountId = data[0].Id;
        } else if (error) {
            console.error('Error fetching account:', error);
        }
    }

    // Step 2: Chained wire dependent on $selectedAccountId
    @wire(getContactsByAccountId, { accountId: '$selectedAccountId' })
    wiredContacts({ data, error }) {
        if (data) {
            this.contacts = data;
        } else if (error) {
            console.error('Error fetching contacts:', error);
        }
    }

    // Step 3: Parallel chained wire also evaluating $selectedAccountId
    @wire(getOpportunitiesByAccountId, { accountId: '$selectedAccountId' })
    wiredOpportunities({ data, error }) {
        if (data) {
            this.opportunities = data;
        } else if (error) {
            console.error('Error fetching opportunities:', error);
        }
    }
}
360 Architecture Summary:
  • Dynamic Reactivity: The '$propertyName' syntax marks a parameter as dynamic and reactive.
  • Client-Side Caching: Results are cached on the client via Lightning Data Service (LDS) architecture.
  • Automatic Cleanup: Wire adapters manage their own lifecycle subscriptions and teardown automatically when the component unmounts.

Step 3: Build the UI Template

Render each section conditionally so the interface displays seamlessly as sequential data resolves:

<template>
    <lightning-card title="Chained Wire Methods Demo" icon-name="standard:hierarchy">
        <div class="slds-p-around_medium">
            
            <!-- Account Section -->
            <div class="slds-m-bottom_medium">
                <h3 class="slds-text-heading_small slds-m-bottom_x-small">Active Account</h3>
                <template lwc:if={accounts}>
                    <template for:each={accounts} for:item="acc">
                        <p key={acc.Id} class="slds-text-body_regular"><strong>{acc.Name}</strong> (ID: {acc.Id})</p>
                    </template>
                </template>
            </div>

            <!-- Chained Contacts Section -->
            <div class="slds-m-bottom_medium">
                <h3 class="slds-text-heading_small slds-m-bottom_x-small">Related Contacts</h3>
                <template lwc:if={contacts}>
                    <ul class="slds-list_dotted">
                        <template for:each={contacts} for:item="con">
                            <li key={con.Id}>{con.Name} - {con.Email}</li>
                        </template>
                    </ul>
                </template>
            </div>

            <!-- Chained Opportunities Section -->
            <div>
                <h3 class="slds-text-heading_small slds-m-bottom_x-small">Related Opportunities</h3>
                <template lwc:if={opportunities}>
                    <ul class="slds-list_dotted">
                        <template for:each={opportunities} for:item="opp">
                            <li key={opp.Id}>{opp.Name} | {opp.StageName}</li>
                        </template>
                    </ul>
                </template>
            </div>

        </div>
    </lightning-card>
</template>

Step 4: Deploy and Verify

Deployment Routine:
# Push component and Apex class to your target scratch or sandbox org
sf project deploy start

# Open target org to test in Lightning App Builder
sf org open
Core Takeaway: Using reactive wire parameters ('$prop') provides automatic dependency tracking, reducing boilerplate code and keeping server calls optimized with client-side caching.