'$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
@wireservice 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
];
}
}
$) 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);
}
}
}
- 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
# 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
'$prop') provides automatic dependency tracking, reducing boilerplate code and keeping server calls optimized with client-side caching.