The short answer: your wire adapter emits before the data arrives, so the property you are reading is undefined at the moment the getter or the template runs. The error is not telling you the record is missing. It is telling you that you wrote code that assumed a wire has two states when it has three.
The error and where it comes from
You will see something like this in the browser console, usually several times on one page load:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'fields')
The clue is the property name in brackets. reading 'fields' means something evaluated somethingUndefined.fields. reading 'data' means the wired property itself did not exist yet. Read that name first, then go looking for the expression that contains it.
A wire has three states, not two
People write the handler as if data or error is always populated. Neither is, at first:
- Pending.
dataanderrorare bothundefined. The component has already rendered. - Loaded.
datais set,errorisundefined. - Failed.
erroris set,dataisundefined.
The order matters. Your connectedCallback runs, your template renders, and only then does the wire resolve. Every getter referenced in that first render executes against the pending state. So this crashes on the very first render, every single time:
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
account;
get ownerName() {
return this.account.data.fields.Owner.displayValue; // boom
}
There is a fourth state people forget: a reactive parameter that has not been set yet. If recordId arrives from the page as a property, the wire fires once with $recordId undefined, gets nothing useful back, and fires again when the value lands.
Fix one: optional chaining, and where it is not enough
Optional chaining short-circuits the whole expression the moment anything in the chain is null or undefined.
get ownerName() {
return this.account?.data?.fields?.Owner?.displayValue ?? '';
}
The ?? at the end matters as much as the ?. in the middle. Without it the getter returns undefined, the template renders an empty string anyway, and a downstream .toUpperCase() throws the same error one line later.
For record data, stop writing that chain entirely. getFieldValue from lightning/uiRecordApi handles undefined input and spanning fields:
import { getFieldValue } from 'lightning/uiRecordApi';
import OWNER_NAME from '@salesforce/schema/Account.Owner.Name';
get ownerName() {
return getFieldValue(this.account.data, OWNER_NAME);
}
Optional chaining does not help with arrays. this.contacts?.data being undefined is safe, but this.contacts.data[0].Name still throws on an empty result set, because an empty array is perfectly defined and has no index zero. Check length.
Fix two: guard the template, do not patch the getters
Cleaner than sprinkling ?. everywhere: render nothing until there is something to render.
<template lwc:if={account.data}>
<p>{ownerName}</p>
</template>
<template lwc:elseif={account.error}>
<p class="slds-text-color_error">Could not load the account.</p>
</template>
<template lwc:else>
<lightning-spinner alternative-text="Loading"></lightning-spinner>
</template>
This is better than the optional-chaining approach because it handles all three states visibly. A user gets a spinner, then either the data or an error. The version that guards only the getters shows a blank panel for both loading and failure, and your support team cannot tell them apart.
The wired-function variant
If you wire a function rather than a property, you are handed the states explicitly and you must handle the empty one yourself:
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
wiredAccount({ data, error }) {
if (data) {
this.name = getFieldValue(data, NAME);
} else if (error) {
this.errorMessage = reduceErrors(error).join(', ');
}
// the pending call falls through both branches, which is correct
}
Assigning this.name = data.fields.Name.value without the if (data) is the same bug in a different shape. The function runs on the pending emission too.
When it is not the wire at all
Two other sources produce the identical message. Reading this.template.querySelector(...) in connectedCallback returns null, because the template has not rendered yet. Move that to renderedCallback. And an Apex method that returns null for a filtered query gives you a defined wired property with data set to null, which passes a truthiness check on the property and fails on the next dot.
Frequently asked
Why does it appear three times per page load? Once per render pass. The initial render, the render after the reactive parameter resolves, and the render after the data arrives.
Should I initialise the property to an empty object? No. That hides the pending state and gives you a component that renders as loaded with nothing in it.
Does this happen with imperative Apex too? Yes, between the call and the resolved promise. The same guard applies, and the template usually needs the same three branches.
