@wire decorator is an automatic data subscription: it caches data, reacts to changes automatically, and serves as your default for reading information. Imperative Apex is a manual phone call: you decide exactly when to make it (like clicking a button or validating a step). Use refreshApex to update your wired data after an imperative action changes the underlying records.
๐ Key Points
- @wire: Declarative, highly efficient, and reactive. Must be used with
cacheable=trueApex methods. - Imperative Apex: Manual execution utilizing JavaScript Promises. Required for DML operations (inserts/updates/deletes) and conditional sequencing.
- Reactivity: Prepending a parameter with
$(e.g.,'$recordId') forces the wire to re-fire automatically whenever that variable changes. - Cache Refresh:
refreshApexis required to update the UI after making data changes via imperative Apex, as the Lightning Data Service (LDS) cache does not automatically know a custom Apex update occurred.
Imagine a related-list component on an Account page that pulls Contacts via a wired Apex method. When users switch between Account tabs in a Console app, they complain that the old Account's Contacts remain stuck on the screen.
The Old/Bad Way: The developer passed the parameter as
{ accountId: recordId }. Because it lacked the dollar sign prefix, the wire captured the ID exactly once upon load and never re-fired. Thinking it was a caching bug, the developer incorrectly added a manual reload function inside renderedCallback, causing a dangerous infinite render loop.The New/Good Way: Simply adding one character fixes everything:
{ accountId: '$recordId' }. The dollar sign marks the variable as reactive, telling the component to automatically re-fetch the data every time the tab changes.
๐ ️ Core Concepts: Fetching & Refreshing
Salesforce provides two distinct ways to call Apex from Lightning Web Components. Understanding when to use which is the foundation of performant UI development.
- @wire (The Reader): Binds a property or function directly to an Apex method. The method must be annotated with
@AuraEnabled(cacheable=true). It participates fully in the Lightning Data Service (LDS) cache, returning{ data, error }. - Imperative (The Actor): Called explicitly within an event handler or lifecycle hook. This is mandatory for DML, as DML methods cannot be cached. It utilizes standard JavaScript `try/catch` and `async/await` patterns.
- refreshApex (The Bridge): If you use imperative Apex to update a record, the wired data on your screen will instantly become stale. You must call
refreshApex(this.wiredResult)to force the wire to fetch the newest truth from the server.
import { LightningElement, api, wire } from 'lwc';
import getContacts from '@salesforce/apex/ContactCtrl.getContacts';
import updateContact from '@salesforce/apex/ContactCtrl.updateContact';
import { refreshApex } from '@salesforce/apex';
export default class ContactList extends LightningElement {
@api recordId;
_wiredContactsResult; // Variable to store the ENTIRE wire response
// The '$' makes recordId reactive. It refires if the ID changes.
@wire(getContacts, { accountId: '$recordId' })
wiredContacts(result) {
this._wiredContactsResult = result; // Keep WHOLE result for refreshApex
if (result.data) {
this.contacts = result.data;
} else if (result.error) {
this.error = result.error;
}
}
async handleSave() {
try {
// Imperative call for DML (non-cacheable)
await updateContact({ contact: this.editedContact });
// Re-provision the wire to update the UI
await refreshApex(this._wiredContactsResult);
} catch (error) {
console.error('Save failed', error);
}
}
}
refreshApex, you must pass the entire provisioned object (the parameter that holds both data and error), not just the `.data` property. Passing this.contacts instead of this._wiredContactsResult will fail silently.
๐งญ 360 Card: @wire vs Imperative Apex
- Rule: Use
@wireas your default for all read-only data. Switch to imperative Apex only when you must control the exact timing of the execution. - Gain: Wires handle their own caching, re-firing, and component lifecycle management entirely behind the scenes with minimal code.
- Reach for Imperative when: A user clicks a button, you need to sequence multiple calls sequentially, or you are executing DML (inserts, updates, deletes).
- Limits: You cannot guarantee exactly when a wire will execute, and the first time it emits, its reactive variables (like recordId) may still be undefined. Always add null-checks in your wired functions.
- Mirror (The UI API alternative): If you are updating a record via imperative Apex that is also tied to standard Lightning components, use
notifyRecordUpdateAvailableinstead ofrefreshApexto update the global LDS cache.
๐ก Core Q&A
Here is the standard operating procedure: Use `@wire` anytime you need to display a related list or read-only summary on load. Use imperative Apex when a user initiates a transaction. Because imperative DML bypasses the LDS cache, your wired properties won't automatically know the database was updated. By capturing the complete wired result in a variable (e.g., `this.wiredResult`) and passing it to `refreshApex(this.wiredResult)` after your imperative call succeeds, you force the component to fetch the latest server data.
Almost certainly, the reactive $ prefix is missing. A parameter only reacts and triggers a new server call when written as a string with a dollar sign (e.g., '$recordId'). If you pass it bare (e.g., this.recordId), it evaluates exactly once during the initial load and becomes permanently stagnant.
The Lightning Data Service (and your wire cache) is completely blind to server-side changes made by integrations, flows, or other users. It will never auto-refresh on its own in these scenarios. You have three primary solutions:
- Manual Push: Provide a manual "Refresh" button that calls
refreshApex. - Event-Driven (Best Practice): Subscribe to a Platform Event or Change Data Capture (CDC) via the
lightning/empApimodule. When the event arrives confirming the integration finished, triggerrefreshApexprogrammatically for true push freshness. - Acceptable Staleness: If real-time accuracy isn't critical, simply display a "Last Updated" timestamp and accept bounded staleness.