The short version: your component is showing a cached value, and which cache decides the fix. Wired Apex is refreshed with refreshApex. Lightning Data Service is refreshed with notifyRecordUpdateAvailable. Using the wrong one produces a component where half the panel updates and the other half does not, which is the symptom that sends people looking for a bug in their own code.
Two caches, not one
A record page is usually holding data from two different places, and they do not know about each other.
Lightning Data Service caches records. Anything using getRecord, lightning-record-form, lightning-record-edit-form or the record view is reading from it, and they share it, which is why one component’s save can update another’s display for free, when both use LDS.
Wired Apex caches method results, keyed on the method and its parameters. Your aggregate, your rollup, your cross-object calculation: cached separately, and completely blind to a record save that happened next to it.
So the rule is mechanical. If the stale value came from Apex, refresh Apex. If it came from a record, notify LDS. If the panel has both, do both.
Refreshing wired Apex
refreshApex needs the wire result, not the value, which means wiring to a function rather than a property:
import { refreshApex } from '@salesforce/apex';
wiredTotal;
@wire(getOpenOpportunityTotal, { accountId: '$recordId' })
wiredOpenTotal(result) {
this.wiredTotal = result; // keep the whole thing
}
async handleSomethingChanged() {
await refreshApex(this.wiredTotal);
}
If you wired straight to a property there is nothing to pass, and no amount of reassignment will help. That is the single most common reason this does not work, and we covered the choice it comes from in wire or imperative.
Refreshing Lightning Data Service
LDS does not take a handle. You tell it a record changed and it re-fetches for everything watching that record:
import { notifyRecordUpdateAvailable } from 'lightning/uiRecordApi';
await notifyRecordUpdateAvailable([{ recordId: this.recordId }]);
You need this when something outside LDS changed the record — an imperative Apex call that did DML, an external process, a flow you launched. If the save went through lightning-record-edit-form or another LDS path, the notification is automatic and you should not be calling anything.
The ordering mistake that makes it look broken
Refreshing before the save has committed gets you the old value back, and it looks exactly like the refresh not working.
// Wrong: the refresh races the save.
updateRecord(fields);
refreshApex(this.wiredTotal);
// Right: wait for the save first.
await updateRecord(fields);
await refreshApex(this.wiredTotal);
Both calls return promises. Awaiting them in order is the whole fix, and the bug is invisible on a fast connection and constant on a slow one, which is how it reaches production.
When the stale value is on another component entirely
Two components on the same page, no parent-child relationship, one saves and the other must react. LDS handles this on its own when both read the same record. When the second component reads Apex, it has no idea anything happened.
The mechanism for that is a message channel: the saving component publishes, the reading component subscribes and calls refreshApex in its handler. It is more moving parts than people expect, which is a reason to prefer LDS for record fields wherever it can express what you need.
The LDS trap: it only caches the fields you asked for
Lightning Data Service caches per record and per field set. A component that requested Name and Industry has those two fields cached. Another component on the same page that asks for Name and AnnualRevenue causes a second fetch, and gets its own entry.
Two consequences. A record that looks refreshed in one component can still be stale in another, because they are watching different field sets. And notifyRecordUpdateAvailable refreshes what is being watched — if a field was never requested, nothing goes and fetches it for you.
When a value simply never appears, check the field list before you check the cache. A field missing from the fields array is not a stale value, it is an absent one, and getFieldValue returns undefined rather than complaining.
What I would check first, in order
- Where did the value come from? Apex or LDS. Everything follows from that and most people skip it.
- Is the wire wired to a function? If not, that is the bug.
- Is the refresh awaited after the save? Not alongside it.
- Did the save actually commit? A validation rule failure leaves the old value in place and the component is telling the truth.
- Is the parameter reactive? A wire with a non-reactive parameter never re-runs, refresh or not.
Frequently asked
Does refreshApex clear the whole Apex cache? No. It refreshes the specific wire result you hand it. Other wired methods keep their cached values.
Can I just reload the page? You can, and users notice. It also loses scroll position and any unsaved state elsewhere on the page.
Why does it work in my sandbox and not in production? Usually the ordering race above: different latency, different outcome. Test on a throttled connection.
Does cacheable=true have to be on the Apex method? For a wired method, yes, and that is also what makes it refreshable. An imperative method cannot be cacheable, and nothing about it is cached, so you simply call it again.
