🏠 Home 🔒 Record Sharing ⚙ Apex Triggers 🔍 SOQL 💻 LWC 🔗 Integration 🤖 Flows & Automation 🤖 Agentforce & AI 🎈 Agentforce Course — Free ☁ Data Cloud 🎓 DC Course — Free 🚀 DevOps Course — Free 💵 CPQ 🎯 100 Scenario Questions 🏆 150 Advanced Questions 📧 Marketing Cloud 🎤 Mock Interview Community 🏗️ Company Wise 👥 About Us Start Learning Free →
Home Interview Q&A

LWC Zero to Hero Module 9 — @wire Adapters

📅
LWC Zero to Hero - Module 9: @wire Adapters | SF Interview Pro
🔌 LWC Zero to Hero — Module 9 of 15

@wire Adapters

Everything so far has used hardcoded or manually-typed data. Now we connect to REAL Salesforce records — declaratively, reactively, automatically.

Module 9 of 15 (counting Module 0 as a bonus prerequisite) · Phase 4: Data Integration (Begins!)
🚀 Welcome to Phase 4. Modules 6-8 taught components how to talk to EACH OTHER. Modules 9-11 teach components how to talk to SALESFORCE DATA — the moment your apps stop being demos and start being real.
🎯 What You'll Master in This Module
@wire is LWC's DECLARATIVE way to fetch data — you describe WHAT data you want, and Salesforce's wire service handles WHEN to fetch it, automatically re-fetching when inputs change, and even caching results behind the scenes via Lightning Data Service. This is fundamentally different from imperatively calling a function yourself (which Module 10 covers) — with @wire, you mostly just declare the need and let the platform do the work.
What @wire actually does, and why it's called "declarative"
The two wire syntaxes — wiring to a property vs wiring to a function
getRecord — fetching a single record's fields the standard way
Reactive wire parameters using the $ syntax
Other standard adapters — getListUi and getObjectInfo
Wiring directly to your own custom Apex methods
Properly handling the {data, error} result shape every time
Concept 1 of 7
What @wire Actually Does
The @wire decorator connects a property or function to a "wire adapter" — a predefined data source (either a standard Salesforce one like getRecord, or your own Apex method). The wire service automatically calls that adapter, populates your data, and AUTOMATICALLY RE-CALLS it whenever any reactive parameter you've wired to changes.
⚡ Why This Matters
"Declarative" means you describe the END RESULT you want ("give me this record's data, keep it updated"), not the step-by-step process of fetching it. This is fundamentally different from writing your own fetch logic — the platform manages timing, caching, and re-fetching for you.
THE SIMPLEST POSSIBLE @wire EXAMPLE
import { LightningElement, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';

export default class AccountName extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
  account;
  // That's it. No connectedCallback(), no manual fetch logic, no loading state code.
  // "account" automatically populates, and re-populates if recordId ever changes.
}
🛠️ Practical Mini-Implementation
Compare this to how you might have approached fetching data BEFORE this course — manually calling an Apex method in connectedCallback() (Module 2), handling the Promise, setting loading flags. @wire replaces all of that boilerplate for the very common case of "I just need this record's data, kept in sync."
⚠️ Common Gotcha
@wire calls happen automatically based on the component's lifecycle and reactive parameter changes — you CANNOT manually trigger a wire to "run now" the way you can call a regular function. If you need on-demand, manually-triggered data fetching, that's exactly what Module 10's imperative Apex calls are for instead.
Concept 2 of 7
Wire Service Syntax — Property Form vs Function Form
There are two ways to write @wire: PROPERTY FORM assigns the result directly to a class property (simplest, good when you just want to read the data in your template). FUNCTION FORM passes the result to a method instead, giving you a chance to run custom logic immediately when new data arrives.
⚡ Why This Matters
Choosing the right form avoids unnecessary complexity — many beginners default to function form out of habit even when property form would be simpler and perfectly sufficient.
PROPERTY FORM — simplest, for direct template use
@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
account;  // no parentheses, no function — just a property name

// In the template: {account.data.fields.Name.value}
FUNCTION FORM — when you need custom logic on each new result
@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
wiredAccount({ data, error }) {  // destructured parameter (recall Module 1, Concept 4)
  if (data) {
    this.accountName = data.fields.Name.value;  // custom transformation logic
    this.logToAnalytics('account_loaded');     // extra side effect, not possible in property form
  } else if (error) {
    this.errorMessage = error.body.message;
  }
}
🛠️ Practical Mini-Implementation
Default to PROPERTY FORM unless you specifically need to run extra logic (like transforming the data into a different shape, or triggering a side effect) the moment new data arrives — function form is for those specific cases, not a universal default.
⚠️ Common Gotcha
In function form, the method name you choose (wiredAccount above) is arbitrary — but it CANNOT be called directly like a normal method elsewhere in your code. It only ever runs automatically when the wire service delivers a new result, not when you call this.wiredAccount() yourself.
Concept 3 of 7
getRecord — Fetching a Single Record
getRecord is the most commonly used standard wire adapter, imported from lightning/uiRecordApi. It fetches a specific record's field values, given a recordId and a list of which fields you want — imported individually using the special @salesforce/schema import path.
⚡ Why This Matters
"Show me this record's data" is the single most common data need across all of Salesforce development — getRecord, backed by Lightning Data Service, handles this with built-in caching and even cross-component data sharing for free.
FETCHING MULTIPLE FIELDS
import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';

const FIELDS = [NAME_FIELD, INDUSTRY_FIELD, REVENUE_FIELD];  // group fields into an array constant

export default class AccountCard extends LightningElement {
  @api recordId;

  @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
  account;

  get accountName() {
    return this.account.data?.fields?.Name?.value;
    // ?. optional chaining — safely access nested data that might not exist yet
  }
}
🛠️ Practical Mini-Implementation
Defining a const FIELDS = [...] array constant (rather than typing the field list inline every time) is standard practice — it keeps your @wire call clean and makes the full set of fields a component depends on easy to see at a glance.
⚠️ Common Gotcha
The data shape is deeply nested: account.data.fields.Name.value — NOT simply account.data.Name. Forgetting the .fields and .value layers is one of the most common beginner mistakes when first working with getRecord results.
Concept 4 of 7
Reactive Parameters — The $ Syntax
The dollar sign prefix in { recordId: '$recordId' } tells the wire service "this value is REACTIVE — watch the this.recordId property, and automatically re-run this wire call whenever it changes." Without the $, the value would be treated as a literal, fixed string instead of a reference to a changing property.
⚡ Why This Matters
Recall Module 5, Concept 6 — when a parent navigates to a different record, the child's @api recordId setter fires with a new value. The $recordId reactive parameter is what makes the wired getRecord call automatically refetch for that new record, without you writing any manual refetch logic.
REACTIVE vs LITERAL — the critical difference
// ✅ REACTIVE — re-fetches whenever this.recordId changes
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
account;

// ❌ LITERAL — treats "recordId" as a hardcoded literal STRING value
// (this would try to fetch a record with the literal Id "recordId" — incorrect!)
@wire(getRecord, { recordId: 'recordId', fields: FIELDS })
account;
REACTIVE PARAMETERS WORK WITH ANY CLASS PROPERTY, NOT JUST @api ONES
export default class OrderSearch extends LightningElement {
  searchTerm = '';  // plain reactive property (Module 4)

  @wire(searchOrders, { term: '$searchTerm' })  // re-runs every time searchTerm changes
  searchResults;

  handleInputChange(event) {
    this.searchTerm = event.target.value;  // triggers the wire to re-run automatically
  }
}
🛠️ Practical Mini-Implementation
This pattern — a plain reactive property feeding a $-prefixed wire parameter — is how you'd build a live search box: as the user types and searchTerm updates, the wire automatically re-queries without you writing any "on keyup, call search" logic yourself (though combining this with Module 6's debounce pattern is wise to avoid firing on every keystroke).
⚠️ Common Gotcha
The $ syntax only works for SIMPLE property references ('$recordId', '$searchTerm') — you cannot use it for computed expressions like '$recordId + something'. If you need a computed value as a wire parameter, compute it into its own separate reactive property first, then reference THAT property with $.
Concept 5 of 7
getListUi & getObjectInfo
Beyond single records, getListUi (from the same lightning/uiListApi module family) fetches a LIST of records based on a list view, and getObjectInfo fetches METADATA about an object itself — its fields, record types, and picklist values — rather than actual record data.
⚡ Why This Matters
Knowing the full family of standard wire adapters (not just getRecord) means you reach for the declarative, cached, built-in solution instead of unnecessarily writing custom Apex for things Salesforce already provides out of the box.
getListUi — fetching records from a list view
import { getListUi } from 'lightning/uiListApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';

@wire(getListUi, {
  objectApiName: ACCOUNT_OBJECT,
  listViewApiName: 'AllAccounts'
})
accountList;
// accountList.data.records.records gives you the array of returned records
getObjectInfo — fetching object METADATA, not record data
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import ORDER_OBJECT from '@salesforce/schema/Order__c';

@wire(getObjectInfo, { objectApiName: ORDER_OBJECT })
orderObjectInfo;
// orderObjectInfo.data.fields gives you EVERY field's metadata (type, label, etc.)
// Commonly used to dynamically build picklist options or check field permissions
🛠️ Practical Mini-Implementation
A common real pattern: use getObjectInfo combined with another adapter, getPicklistValues, to dynamically populate a picklist's options directly from the object's actual configured values — meaning if an admin adds a new picklist value in Setup, your component automatically reflects it with zero code changes.
⚠️ Common Gotcha
getObjectInfo returns METADATA (field definitions, types, labels) — it does NOT return any actual record data. Confusing it with getRecord (which returns actual field VALUES for a specific record) is a common mix-up; remember "Info" in the name signals metadata, not data.
Concept 6 of 7
Wiring to Apex Methods
Beyond standard adapters, you can @wire directly to your OWN Apex method, getting all the same declarative, reactive, automatically-refetching behavior — but with completely custom query logic. The Apex method must be marked @AuraEnabled(cacheable=true) to be wire-eligible.
⚡ Why This Matters
Standard adapters cover common cases, but real apps frequently need custom queries — joining data, filtering by complex criteria, or returning a shape standard adapters can't produce. Wiring to custom Apex gives you full query flexibility while keeping the declarative wire benefits.
THE APEX METHOD — must be cacheable=true to support @wire
public class OrderController {
  @AuraEnabled(cacheable=true)
  public static List<Order__c> getOrdersForAccount(Id accountId) {
    return [SELECT Id, Name, Amount__c
            FROM Order__c
            WHERE Account__c = :accountId];
  }
}
WIRING TO IT FROM LWC — looks just like a standard adapter
import { LightningElement, api, wire } from 'lwc';
import getOrdersForAccount from '@salesforce/apex/OrderController.getOrdersForAccount';

export default class AccountOrders extends LightningElement {
  @api recordId;

  @wire(getOrdersForAccount, { accountId: '$recordId' })
  orders;
  // Same $ reactive syntax, same {data, error} result shape — fully consistent
}
🛠️ Practical Mini-Implementation
Recall Module 6's @salesforce/apex/... default import pattern — wiring to Apex uses the EXACT same import syntax as the imperative calls covered in Module 10. The difference isn't in the import; it's in HOW you use the imported function — passed to @wire(...) declaratively here, versus called directly as a function imperatively in Module 10.
⚠️ Common Gotcha
Forgetting cacheable=true on the Apex method's @AuraEnabled annotation causes a deployment or runtime error when you try to @wire to it — the wire service REQUIRES this annotation specifically because caching and predictable re-fetching are core to how the wire service operates. Without it, the method can only be called imperatively (Module 10), never wired.
Concept 7 of 7
Handling data/error Properly
Every wire result — standard adapter or custom Apex — arrives in the same consistent shape: an object with data (populated on success) and error (populated on failure), recall this exact destructuring pattern from Module 1, Concept 4. Building a consistent habit for checking both is essential for robust components.
⚡ Why This Matters
A component that only handles the "happy path" (data exists) and ignores errors will show a blank, confusing screen when something goes wrong — properly handling both states is the difference between a demo-quality and production-quality component.
THE STANDARD PATTERN — handling all three possible states
export default class AccountOrders extends LightningElement {
  @api recordId;
  orders;
  error;

  @wire(getOrdersForAccount, { accountId: '$recordId' })
  wiredOrders({ data, error }) {
    if (data) {
      this.orders = data;
      this.error = undefined;
    } else if (error) {
      this.error = error.body?.message ?? 'Unknown error occurred';
      this.orders = undefined;
    }
  }

  get isLoading() {
    return !this.orders && !this.error;  // neither populated yet = still loading
  }
}
🛠️ Practical Mini-Implementation
The isLoading getter pattern shown above directly reuses Module 3's "loading / empty / populated" template pattern — combine this wire result handling with that template structure, and you have a complete, robust data-driven component: loading state while neither data nor error has arrived, error state if it fails, and the populated list once data arrives.
⚠️ Common Gotcha — Module Wrap-Up
Never assume a wire call will always succeed and skip the error branch "just to keep the code simple" — network issues, permission errors, or invalid Ids are all realistic failure scenarios in production. A component without error handling doesn't fail gracefully; it fails confusingly, showing the user a blank screen with no explanation.
💬 Module 9 Interview Questions (6)
Q1What does it mean that @wire is "declarative," and how does this differ fundamentally from imperatively calling a function to fetch data?
Declarative means you describe WHAT data you need and let the platform's wire service handle WHEN and HOW to fetch it, including automatic re-fetching whenever reactive parameters change, all without writing explicit fetch-triggering logic yourself. Imperative data fetching, by contrast, requires you to explicitly call a function at a specific moment in your code (such as inside an event handler or connectedCallback), manually manage when re-fetching should happen, and handle the resulting Promise yourself. With @wire, you write a property or function decorated with @wire once, and the platform manages the entire fetch lifecycle; with imperative calls, you are responsible for triggering and managing that lifecycle yourself.
"@wire is declarative because you describe the data need once and the platform automatically manages fetching and re-fetching based on reactive parameter changes, while imperative calls require you to explicitly trigger the fetch yourself and manage the resulting Promise."
Q2What is the difference between property form and function form when using @wire, and when would you specifically choose function form?
Property form assigns the wire result directly to a class property with no parentheses or method body, making it the simplest option when you just need to read the resulting data or error directly in your template or in getters. Function form instead passes the wire result to a method, giving you the opportunity to run custom logic — such as transforming the data into a different shape, triggering a side effect, or splitting data and error handling into separate assigned properties — the moment a new result arrives. Function form should be chosen specifically when you need this kind of custom processing beyond simply storing the raw result, rather than being used as a default regardless of need.
"Property form simply assigns the wire result to a class property for direct use; function form passes the result to a method instead, and should be chosen specifically when you need custom logic or transformation to run each time a new result arrives, not as a default."
Q3In the code @wire(getRecord, { recordId: '$recordId', fields: FIELDS }), what is the purpose of the dollar sign before recordId, and what would happen if it were omitted?
The dollar sign marks recordId as a REACTIVE parameter, telling the wire service to watch the component's this.recordId property and automatically re-execute the wire call whenever that property's value changes. If the dollar sign were omitted, writing recordId: 'recordId' instead, the wire service would treat "recordId" as a literal, hardcoded string value rather than a reference to the changing property, attempting to fetch a record using the literal text "recordId" as its Id, which would not correctly reflect the component's actual recordId property and would not update reactively when that property changes.
"The dollar sign marks a wire parameter as reactive, telling the wire service to watch that property and automatically re-fetch when it changes; omitting it causes the value to be treated as a literal hardcoded string instead of a reference to the changing property."
Q4What is the difference between getRecord and getObjectInfo, and why might a developer new to LWC confuse the two?
getRecord fetches actual field VALUES for one specific record identified by its recordId, while getObjectInfo fetches METADATA about an object as a whole — such as its available fields, their data types, record types, and picklist value sets — without referencing or returning any specific record's actual data at all. Developers may confuse the two because both deal broadly with "object" concepts and are imported from similarly named modules (lightning/uiRecordApi versus lightning/uiObjectInfoApi), but the key distinction is that getRecord returns values for one record instance, while getObjectInfo returns the structural definition of the object type itself, independent of any particular record.
"getRecord returns actual field values for one specific record, while getObjectInfo returns structural metadata about an object type itself (its fields, types, picklist values) with no connection to any specific record — the word 'Info' signals metadata, not record data."
Q5What specific requirement must a custom Apex method satisfy to be usable with @wire, and what happens if that requirement is not met?
The Apex method must be annotated with @AuraEnabled(cacheable=true) — the cacheable=true portion specifically signals to the platform that the method is safe to be called repeatedly and have its results cached and reused, which is a core requirement for how the wire service's automatic caching and re-fetching behavior operates. If this annotation is missing or set to cacheable=false, attempting to use the method with @wire will fail, since the wire service's caching and reactive re-fetch model depends on this guarantee; such a method could still be called imperatively (covered in the next module) but cannot be wired declaratively.
"An Apex method must be annotated with @AuraEnabled(cacheable=true) to support @wire, since the wire service's caching and reactive re-fetching model depends on that guarantee — without it, the method can only be called imperatively, never wired."
Q6A component only handles the data case of a wire result and ignores the error case entirely. What problems could this cause in production, and what would a more robust implementation look like?
Ignoring the error case means that if the wire call fails for any reason — such as a permission issue, an invalid recordId, or a network problem — the user is presented with a blank or confusingly empty interface with no indication of what went wrong or why, rather than any helpful feedback. A more robust implementation destructures both data and error from the wire result, explicitly checking and handling each case separately: populating the relevant display data when data is present, and setting a clear, user-facing error message (often derived from error.body.message) when error is present instead, ideally combined with a loading state getter that reflects whether neither data nor error has arrived yet, covering all three realistic states a wired component can be in.
"Ignoring the error case leaves users with a confusing blank screen on failure instead of helpful feedback; a robust implementation explicitly handles both data and error branches, surfacing a clear error message when error is present, alongside a loading state for when neither has arrived yet."
📝 Module 9 Recap — @wire Adapters Mastered
✅ @wire is declarative — describe what data you need, the platform handles when/how to fetch and re-fetch it
✅ Property form for simple direct use; function form when custom logic needs to run on each new result
✅ getRecord fetches a record's field values; remember the nested data.fields.FieldName.value shape
✅ The $ prefix marks a wire parameter as reactive, watching a property and auto-refetching on change
✅ getObjectInfo returns object metadata (not record data); getListUi returns records from a list view
✅ Custom Apex methods need @AuraEnabled(cacheable=true) to be wire-eligible
✅ Always handle both data and error branches — never assume the happy path is the only path
🎯 Before Moving to Module 10...
Try this: build a component wiring to getRecord for an Account, displaying Name and Industry, with a reactive recordId from @api. Then add a second wired Apex method fetching related Orders for that Account, using the same $recordId reactive parameter. Combine both into one component using Module 3's loading/empty/populated pattern. Module 10 covers the OTHER half of data fetching — imperative Apex calls — and importantly, when to reach for that instead of @wire.
☕ Enjoyed this article?
SF Interview Pro is 100% free and maintained by a Salesforce professional. No ads, no paywalls, and no signup required. If this guide helped you prepare for an interview, earn a certification, or grow your Salesforce career, consider buying me a coffee! ☕💜
🇮🇳 UPI (India)
UPI QR Code to support sfinterviewpro
Pay by QR
GPay · PhonePe · Paytm · BHIM
🌎 International
PayPal QR Code to support sfinterviewpro
Scan or tap to pay