The Decision in One Paragraph
Use @wire when the data is read-only, reactive to input changes, and benefits from Lightning Data Service caching. Use imperative Apex when the call is user-triggered, has side effects (DML), or needs fine-grained control over timing, error handling, or cancellation. The two aren’t interchangeable — they have different semantics.
What @wire Is
@wire is a declarative data binding. You tell LWC “here’s a method; when its inputs change, re-run it and hand me the result.” The framework calls the Apex method, caches the result, and re-invokes when inputs change.
import { LightningElement, wire } from 'lwc';
import getCases from '@salesforce/apex/CaseService.getCasesForAccount';
export default class CaseList extends LightningElement {
@api recordId;
@wire(getCases, { accountId: '$recordId' })
cases;
}
When recordId changes, cases re-populates. No manual refetch.
What Imperative Is
Imperative means you call the Apex method directly, like a regular async function:
import getCases from '@salesforce/apex/CaseService.getCasesForAccount';
async handleClick() {
try {
const result = await getCases({ accountId: this.recordId });
this.cases = result;
} catch (e) {
this.error = e;
}
}
You control when it runs, what to do with the result, and how to handle errors.
Side-by-Side
| Concern | @wire | Imperative |
|---|---|---|
| When it fires | Inputs change | You call it |
| Caching | Yes (Lightning Data Service) | No (you manage) |
| Return shape | { data, error } object | Resolved value or thrown |
| DML calls | Cannot call methods with DML | Can (if Apex permits) |
| Cancellation | Automatic on unmount | You handle |
| User trigger | Awkward — must toggle an input | Natural — just call |
| Re-run on same inputs | No (cached) | Yes, every call |
Use @wire When…
Data is read-only. Apex methods wired via @wire should be @AuraEnabled(cacheable=true). That annotation prevents DML in the method and enables caching.
Data depends on reactive inputs. When recordId or a filter value changes, you want automatic refetch.
You benefit from caching. If 10 components on the same page wire the same method with the same inputs, they share one cached result.
You don’t need precise error recovery. @wire exposes { data, error } but doesn’t give you retry or fallback hooks.
Use Imperative When…
The call has side effects. Any Apex method that writes data must be called imperatively, by design.
The call is user-triggered. Button click, form submit, menu action — call imperatively on the handler.
You need fine-grained error handling. Imperative try/catch gives you complete control — retry, fallback, user-facing message, all tailored.
You need to await multiple calls sequentially. Imperative composition is natural; @wire composition is awkward.
You need one-shot fetches, not reactive. A load-on-mount that never needs to re-fetch can be imperative in connectedCallback.
Caching Mechanics
@wire + cacheable=true uses Lightning Data Service (LDS) cache:
- First call hits the server, result cached.
- Subsequent calls with the same inputs return instantly from cache.
- Cache is shared across components on the same page.
- Cache invalidates on record updates via LDS, and can be manually refreshed.
Imperative calls bypass the cache entirely. Every imperative call is a round trip.
For read-heavy UIs, @wire + caching is a significant performance win. For write-heavy UIs, caching is irrelevant and imperative gives more control.
Refreshing Wired Data
When you mutate data imperatively, wired components displaying that data need to know to refresh. Use refreshApex:
import { refreshApex } from '@salesforce/apex';
@wire(getCases, { accountId: '$recordId' })
wiredCases;
async handleDelete(event) {
await deleteCase({ caseId: event.target.dataset.id });
await refreshApex(this.wiredCases); // re-fetches and updates
}
The key: assign the wire result to a named property (wiredCases), pass that property to refreshApex. Without this, your imperative mutation won’t reflect in the wired display.
Reactive Inputs: The $ Syntax
Prefix a property name with $ in the wire config to make it reactive:
@wire(getCases, { accountId: '$recordId' })
cases;
When this.recordId changes, the wire re-runs. Without the $, the wire binds to the string 'recordId', not the property’s value.
Forgetting the $ is a common bug. The wire fires once and never updates.
Error Handling Patterns
With @wire:
@wire(getCases, { accountId: '$recordId' })
wiredCases({ data, error }) {
if (data) this.cases = data;
if (error) this.error = error;
}
The function-style wire lets you react to errors (toast, fallback). You don’t get retry for free — you’d have to trigger a re-wire manually.
Imperative:
try {
const result = await getCases({ accountId: this.recordId });
this.cases = result;
} catch (e) {
// retry, log, display, anything
}
Full control.
Common Mistakes
Wiring a method with DML. @wire requires cacheable=true, which requires no DML. If you see “Method is not cacheable” errors, this is why.
Forgetting $ for reactive inputs. Wire fires once and never updates.
Using @wire for everything. Turns user-triggered calls into awkward toggles of reactive flags to force re-wire.
Not calling refreshApex after imperative writes. Wired components display stale data until the page reloads.
Catching errors in @wire but doing nothing. If the error path just logs, the user sees an empty component with no explanation. Show something.
Performance Notes
For small apps, either pattern is fine. At scale:
@wire+ caching meaningfully reduces server calls.- Imperative gives predictable call patterns, easier to trace.
- A hybrid — wire for initial reads, imperative for mutations,
refreshApexto sync — is the most common real-world shape.
Frequently Asked Questions
Can I use @wire to call methods with @AuraEnabled but not cacheable?
No — @wire requires cacheable=true. The two are coupled.
How do I cancel a wire?
You can’t cancel the underlying call mid-flight. But the wire automatically unsubscribes when the component unmounts, so stale results won’t affect a disconnected component.
Can imperative calls benefit from LDS caching?
Not directly. You can use LDS’s getRecord wire for standard UI API-backed reads, which benefits from caching even in imperative patterns via getRecordNotifyChange.
What about @wire with graphQL?
LWC supports @wire with GraphQL via the lightning/uiGraphQLApi module. Same principles — reactive inputs, LDS cache, read-only.