LWC: Wire Service vs Imperative Apex — When to Use Each
Lightning Web Components give you two ways to call Apex: the wire service and imperative calls. The wire service is reactive — it automatically re-fetches data when input parameters change and caches results. Imperative calls are manual — you call them explicitly and handle the promise yourself. Using the wrong one creates bugs that are painful to debug.
Use the wire service when you need data on component load and want automatic reactivity. If a parent component passes a new recordId, a wired method automatically re-fetches. The cache is smart: multiple components wiring to the same Apex method with the same parameters share a single server call. But the wire service has constraints. You cannot control when it fires. You cannot call it conditionally. You cannot use it for DML operations (insert, update, delete).
Use imperative Apex for user-initiated actions (button clicks, form submissions), when you need conditional logic before calling the server, or for any DML operation. Always wrap imperative calls in try/catch and handle loading states. The most common bug I see: developers use @wire for a method that does DML and wonder why it fires on component load, inserting duplicate records.
Code Example
// WIRE SERVICE — reactive, cached, automatic
import { LightningElement, wire, api } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';
export default class ContactList extends LightningElement {
@api recordId;
contacts;
error;
// Re-fetches automatically when recordId changes
@wire(getContacts, { accountId: '$recordId' })
wiredContacts({ error, data }) {
if (data) {
this.contacts = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.contacts = undefined;
}
}
}
// IMPERATIVE — manual, for user actions & DML
import saveContact from '@salesforce/apex/ContactController.saveContact';
export default class ContactForm extends LightningElement {
isLoading = false;
async handleSave() {
this.isLoading = true;
try {
const result = await saveContact({
firstName: this.firstName,
lastName: this.lastName,
accountId: this.recordId
});
this.dispatchEvent(new ShowToastEvent({
title: 'Success',
message: 'Contact created',
variant: 'success'
}));
} catch (error) {
this.dispatchEvent(new ShowToastEvent({
title: 'Error',
message: error.body?.message || 'Unknown error',
variant: 'error'
}));
} finally {
this.isLoading = false;
}
}
}Need this implemented in your org?
I've shipped these patterns in production for 10+ years.
View Consulting →Enjoyed this? Get more like it.
Glen's Musings — AI, investing, and building things. Occasional. Free.
More LWC Tips
LWC Event Handling: Parent-Child, Sibling, and Cross-DOM Communication
Event handling in LWC is where most developers get stuck. The framework gives you three patterns and choosing the wrong ...
Read moreAdvancedTesting LWC with Jest: The Patterns That Actually Work in CI/CD
Jest testing for LWC is powerful but the setup and patterns are different from standard JavaScript testing. The key insi...
Read more