fe-lwc-skill 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/bin/index.js +145 -0
- package/package.json +23 -0
- package/skill/SKILL.md +88 -0
- package/skill/rules/data-error-handling.md +297 -0
- package/skill/rules/data-lds-first.md +261 -0
- package/skill/rules/data-wire-vs-imperative.md +248 -0
- package/skill/rules/design-token-dxp.md +230 -0
- package/skill/rules/design-token-slds.md +258 -0
- package/skill/rules/events-custom-event.md +227 -0
- package/skill/rules/events-lms.md +193 -0
- package/skill/rules/template-directives.md +191 -0
- package/skill/rules/template-dom-querying.md +202 -0
- package/skill/rules/template-form-pattern.md +285 -0
- package/skill/rules/template-track-immutable.md +206 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# data-lds-first
|
|
2
|
+
|
|
3
|
+
**Impact: CRITICAL**
|
|
4
|
+
|
|
5
|
+
Use `lightning/uiRecordApi` for standard CRUD and record fetching before writing any Apex controller. LDS is backed by the **UI API cache** — the platform automatically deduplicates requests and shares cached data across all components on the page.
|
|
6
|
+
|
|
7
|
+
> **Rule:** If the operation is get / create / update / delete on a standard or custom object, reach for LDS first. Only fall back to Apex when LDS cannot cover the requirement (e.g. complex queries, aggregates, multi-object logic).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Get a single record
|
|
12
|
+
|
|
13
|
+
**Incorrect — unnecessary Apex call, no caching:**
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// patientCard.ts
|
|
17
|
+
import { LightningElement, api, wire } from "lwc";
|
|
18
|
+
import getPatient from "@salesforce/apex/PatientController.getPatient";
|
|
19
|
+
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
export default class PatientCard extends LightningElement {
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
@api recordId: string;
|
|
24
|
+
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
@wire(getPatient, { recordId: "$recordId" })
|
|
27
|
+
patient: { data?: unknown; error?: unknown };
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct — LDS handles caching, no SOQL consumed:**
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// patientCard.ts
|
|
35
|
+
import { LightningElement, api, wire } from "lwc";
|
|
36
|
+
import { getRecord, getFieldValue } from "lightning/uiRecordApi";
|
|
37
|
+
import NAME_FIELD from "@salesforce/schema/Patient__c.Name";
|
|
38
|
+
import DOB_FIELD from "@salesforce/schema/Patient__c.Date_of_Birth__c";
|
|
39
|
+
|
|
40
|
+
type RecordResult = { data?: Record<string, unknown>; error?: unknown };
|
|
41
|
+
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
export default class PatientCard extends LightningElement {
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
@api recordId: string;
|
|
46
|
+
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
@wire(getRecord, { recordId: "$recordId", fields: [NAME_FIELD, DOB_FIELD] })
|
|
49
|
+
patient: RecordResult;
|
|
50
|
+
|
|
51
|
+
public get patientName(): string | null {
|
|
52
|
+
return getFieldValue(this.patient.data, NAME_FIELD) as string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public get patientDob(): string | null {
|
|
56
|
+
return getFieldValue(this.patient.data, DOB_FIELD) as string | null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<!-- patientCard.html -->
|
|
63
|
+
<template>
|
|
64
|
+
<template lwc:if="{patient.data}">
|
|
65
|
+
<p>{patientName}</p>
|
|
66
|
+
<p>{patientDob}</p>
|
|
67
|
+
</template>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Create a record
|
|
74
|
+
|
|
75
|
+
**Incorrect — Apex DML, counts against DML governor limit per call:**
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// patientForm.ts
|
|
79
|
+
import { LightningElement } from "lwc";
|
|
80
|
+
import createPatient from "@salesforce/apex/PatientController.createPatient";
|
|
81
|
+
|
|
82
|
+
interface PatientFormState {
|
|
83
|
+
name: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
export default class PatientForm extends LightningElement {
|
|
88
|
+
formState: PatientFormState = { name: "" };
|
|
89
|
+
|
|
90
|
+
public handleChange(event: Event): void {
|
|
91
|
+
const target = event.target as HTMLInputElement;
|
|
92
|
+
this.formState = { ...this.formState, [target.name]: target.value };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public async handleSubmit(event: Event): Promise<void> {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
await createPatient({ fields: this.formState }); // ❌ bypasses LDS cache
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Correct — LDS `createRecord`, platform handles DML and cache invalidation:**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// patientForm.ts
|
|
106
|
+
import { LightningElement } from "lwc";
|
|
107
|
+
import { createRecord } from "lightning/uiRecordApi";
|
|
108
|
+
import PATIENT_OBJECT from "@salesforce/schema/Patient__c";
|
|
109
|
+
import NAME_FIELD from "@salesforce/schema/Patient__c.Name";
|
|
110
|
+
|
|
111
|
+
interface PatientFormState {
|
|
112
|
+
name: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// @ts-ignore
|
|
116
|
+
export default class PatientForm extends LightningElement {
|
|
117
|
+
formState: PatientFormState = { name: "" };
|
|
118
|
+
|
|
119
|
+
public handleChange(event: Event): void {
|
|
120
|
+
const target = event.target as HTMLInputElement;
|
|
121
|
+
this.formState = { ...this.formState, [target.name]: target.value };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public async handleSubmit(event: Event): Promise<void> {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
const fields: Record<string, string> = {
|
|
127
|
+
[NAME_FIELD.fieldApiName]: this.formState.name,
|
|
128
|
+
};
|
|
129
|
+
await createRecord({ apiName: PATIENT_OBJECT.objectApiName, fields });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Update a record
|
|
137
|
+
|
|
138
|
+
**Incorrect:**
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// patientEditForm.ts
|
|
142
|
+
import { LightningElement, api } from "lwc";
|
|
143
|
+
import updatePatient from "@salesforce/apex/PatientController.updatePatient";
|
|
144
|
+
|
|
145
|
+
// @ts-ignore
|
|
146
|
+
export default class PatientEditForm extends LightningElement {
|
|
147
|
+
// @ts-ignore
|
|
148
|
+
@api recordId: string;
|
|
149
|
+
formState = { name: "" };
|
|
150
|
+
|
|
151
|
+
public handleChange(event: Event): void {
|
|
152
|
+
const target = event.target as HTMLInputElement;
|
|
153
|
+
this.formState = { ...this.formState, [target.name]: target.value };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public async handleSave(): Promise<void> {
|
|
157
|
+
await updatePatient({ recordId: this.recordId, fields: this.formState }); // ❌
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Correct — `updateRecord` automatically refreshes all components holding the same record:**
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// patientEditForm.ts
|
|
166
|
+
import { LightningElement, api } from "lwc";
|
|
167
|
+
import { updateRecord } from "lightning/uiRecordApi";
|
|
168
|
+
import ID_FIELD from "@salesforce/schema/Patient__c.Id";
|
|
169
|
+
import NAME_FIELD from "@salesforce/schema/Patient__c.Name";
|
|
170
|
+
|
|
171
|
+
// @ts-ignore
|
|
172
|
+
export default class PatientEditForm extends LightningElement {
|
|
173
|
+
// @ts-ignore
|
|
174
|
+
@api recordId: string;
|
|
175
|
+
formState = { name: "" };
|
|
176
|
+
|
|
177
|
+
public handleChange(event: Event): void {
|
|
178
|
+
const target = event.target as HTMLInputElement;
|
|
179
|
+
this.formState = { ...this.formState, [target.name]: target.value };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async handleSave(): Promise<void> {
|
|
183
|
+
const fields: Record<string, string> = {
|
|
184
|
+
[ID_FIELD.fieldApiName]: this.recordId,
|
|
185
|
+
[NAME_FIELD.fieldApiName]: this.formState.name,
|
|
186
|
+
};
|
|
187
|
+
await updateRecord({ fields });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Delete a record
|
|
195
|
+
|
|
196
|
+
**Incorrect:**
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// patientListItem.ts
|
|
200
|
+
import { LightningElement, api } from "lwc";
|
|
201
|
+
import deletePatient from "@salesforce/apex/PatientController.deletePatient";
|
|
202
|
+
|
|
203
|
+
// @ts-ignore
|
|
204
|
+
export default class PatientListItem extends LightningElement {
|
|
205
|
+
// @ts-ignore
|
|
206
|
+
@api recordId: string;
|
|
207
|
+
|
|
208
|
+
public async handleDelete(): Promise<void> {
|
|
209
|
+
await deletePatient({ recordId: this.recordId }); // ❌
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Correct:**
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// patientListItem.ts
|
|
218
|
+
import { LightningElement, api } from "lwc";
|
|
219
|
+
import { deleteRecord } from "lightning/uiRecordApi";
|
|
220
|
+
|
|
221
|
+
// @ts-ignore
|
|
222
|
+
export default class PatientListItem extends LightningElement {
|
|
223
|
+
// @ts-ignore
|
|
224
|
+
@api recordId: string;
|
|
225
|
+
|
|
226
|
+
public async handleDelete(): Promise<void> {
|
|
227
|
+
await deleteRecord(this.recordId);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## When LDS is NOT enough — fall back to Apex
|
|
235
|
+
|
|
236
|
+
Use Apex when the operation requires `WHERE`, `ORDER BY`, aggregates, joins, or complex server-side logic.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// appointmentList.ts — acceptable Apex fallback
|
|
240
|
+
import { LightningElement, api, wire } from "lwc";
|
|
241
|
+
import getAppointmentsByPatient from "@salesforce/apex/AppointmentController.getAppointmentsByPatient";
|
|
242
|
+
|
|
243
|
+
interface AppointmentRecord {
|
|
244
|
+
Id: string;
|
|
245
|
+
Name: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
export default class AppointmentList extends LightningElement {
|
|
250
|
+
// @ts-ignore
|
|
251
|
+
@api recordId: string;
|
|
252
|
+
|
|
253
|
+
// @ts-ignore
|
|
254
|
+
@wire(getAppointmentsByPatient, { patientId: "$recordId" })
|
|
255
|
+
appointments: { data?: AppointmentRecord[]; error?: unknown };
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
**Reference:** [lightning/uiRecordApi — Salesforce LWC Developer Guide](https://developer.salesforce.com/docs/platform/lwc/guide/reference-lightning-ui-api-record.html)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# data-wire-vs-imperative
|
|
2
|
+
|
|
3
|
+
**Impact: HIGH**
|
|
4
|
+
|
|
5
|
+
`@wire` and imperative Apex calls serve different purposes. Using the wrong one leads to either components that cannot react to user actions, or unnecessary re-fetches that bypass the LDS cache.
|
|
6
|
+
|
|
7
|
+
> **Rule:**
|
|
8
|
+
>
|
|
9
|
+
> - Use `@wire` when data should load automatically and stay in sync with reactive properties.
|
|
10
|
+
> - Use imperative calls when data fetch must be triggered explicitly by a user action.
|
|
11
|
+
> - After an imperative DML that affects `@wire` Apex data, call `refreshApex` to sync the wire cache.
|
|
12
|
+
> - Do **not** call `refreshApex` on LDS wire adapters (`getRecord`, etc.) — LDS self-invalidates after `updateRecord` / `deleteRecord`.
|
|
13
|
+
> - To use `refreshApex`, the wire result must be stored as a **property** (not destructured).
|
|
14
|
+
|
|
15
|
+
> **Note on import path:** `refreshApex` moved to `lightning/apex` in API v59+. Use `@salesforce/apex` for older API versions.
|
|
16
|
+
>
|
|
17
|
+
> ```typescript
|
|
18
|
+
> // API v59+ (LWR / newer orgs)
|
|
19
|
+
> import { refreshApex } from "lightning/apex";
|
|
20
|
+
> // API v58 and below
|
|
21
|
+
> import { refreshApex } from "@salesforce/apex";
|
|
22
|
+
> ```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Case 1: Using `@wire` for an operation that needs to be triggered by user action
|
|
27
|
+
|
|
28
|
+
**Incorrect — trying to trigger `@wire` from a button click:**
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// appointmentSearch.ts
|
|
32
|
+
import { LightningElement, wire } from "lwc";
|
|
33
|
+
import searchAppointments from "@salesforce/apex/AppointmentController.searchAppointments";
|
|
34
|
+
|
|
35
|
+
interface AppointmentRecord {
|
|
36
|
+
Id: string;
|
|
37
|
+
Name: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
export default class AppointmentSearch extends LightningElement {
|
|
42
|
+
searchTerm: string = "";
|
|
43
|
+
|
|
44
|
+
// Fires on component load — cannot be manually re-triggered.
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
@wire(searchAppointments, { term: "$searchTerm" })
|
|
47
|
+
appointments: { data?: AppointmentRecord[]; error?: unknown };
|
|
48
|
+
|
|
49
|
+
public handleSearch(): void {
|
|
50
|
+
// Does nothing — @wire already ran on load.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Correct — use imperative call inside the event handler:**
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// appointmentSearch.ts
|
|
59
|
+
import { LightningElement } from "lwc";
|
|
60
|
+
import searchAppointments from "@salesforce/apex/AppointmentController.searchAppointments";
|
|
61
|
+
import { reduceErrors } from "c/utils";
|
|
62
|
+
|
|
63
|
+
interface AppointmentRecord {
|
|
64
|
+
Id: string;
|
|
65
|
+
Name: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
export default class AppointmentSearch extends LightningElement {
|
|
70
|
+
searchTerm: string = "";
|
|
71
|
+
appointments: AppointmentRecord[] = [];
|
|
72
|
+
isLoading: boolean = false;
|
|
73
|
+
error: unknown = null;
|
|
74
|
+
|
|
75
|
+
public handleChange(event: Event): void {
|
|
76
|
+
this.searchTerm = (event.target as HTMLInputElement).value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async handleSearch(): Promise<void> {
|
|
80
|
+
this.isLoading = true;
|
|
81
|
+
this.error = null;
|
|
82
|
+
try {
|
|
83
|
+
this.appointments = await searchAppointments({ term: this.searchTerm });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
this.error = e;
|
|
86
|
+
} finally {
|
|
87
|
+
this.isLoading = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Case 2: Using imperative call in `connectedCallback` when `@wire` would suffice
|
|
96
|
+
|
|
97
|
+
**Incorrect — imperative in `connectedCallback`, does not react to `recordId` changes:**
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// patientSummary.ts
|
|
101
|
+
import { LightningElement, api } from "lwc";
|
|
102
|
+
import getPatientSummary from "@salesforce/apex/PatientController.getPatientSummary";
|
|
103
|
+
|
|
104
|
+
interface PatientSummaryRecord {
|
|
105
|
+
Name: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// @ts-ignore
|
|
109
|
+
export default class PatientSummary extends LightningElement {
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
@api recordId: string;
|
|
112
|
+
summary: PatientSummaryRecord | null = null;
|
|
113
|
+
|
|
114
|
+
// Runs once on mount. Does not re-run if recordId changes.
|
|
115
|
+
// Bypasses LDS cache — always fires a new SOQL query.
|
|
116
|
+
public async connectedCallback(): Promise<void> {
|
|
117
|
+
this.summary = await getPatientSummary({ recordId: this.recordId });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Correct — `@wire` reacts to `recordId` changes and uses LDS cache:**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// patientSummary.ts
|
|
126
|
+
import { LightningElement, api, wire } from "lwc";
|
|
127
|
+
import getPatientSummary from "@salesforce/apex/PatientController.getPatientSummary";
|
|
128
|
+
|
|
129
|
+
interface PatientSummaryRecord {
|
|
130
|
+
Name: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// @ts-ignore
|
|
134
|
+
export default class PatientSummary extends LightningElement {
|
|
135
|
+
// @ts-ignore
|
|
136
|
+
@api recordId: string;
|
|
137
|
+
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
@wire(getPatientSummary, { recordId: "$recordId" })
|
|
140
|
+
summary: { data?: PatientSummaryRecord; error?: unknown };
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Case 3: Syncing `@wire` Apex data after imperative DML — `refreshApex`
|
|
147
|
+
|
|
148
|
+
**Incorrect — wire result destructured, `refreshApex` cannot be called:**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// appointmentList.ts
|
|
152
|
+
import { LightningElement, api, wire } from "lwc";
|
|
153
|
+
import { refreshApex } from "lightning/apex";
|
|
154
|
+
import getAppointments from "@salesforce/apex/AppointmentController.getAppointments";
|
|
155
|
+
import deleteAppointment from "@salesforce/apex/AppointmentController.deleteAppointment";
|
|
156
|
+
|
|
157
|
+
// @ts-ignore
|
|
158
|
+
export default class AppointmentList extends LightningElement {
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
@api recordId: string;
|
|
161
|
+
appointments: unknown[] = [];
|
|
162
|
+
|
|
163
|
+
// ❌ Destructured — refreshApex has nothing to reference
|
|
164
|
+
// @ts-ignore
|
|
165
|
+
@wire(getAppointments, { recordId: "$recordId" })
|
|
166
|
+
public handleAppointments({ data, error }: { data?: unknown[]; error?: unknown }): void {
|
|
167
|
+
this.appointments = data ?? [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public async handleDelete(event: Event): Promise<void> {
|
|
171
|
+
const target = event.target as HTMLElement;
|
|
172
|
+
await deleteAppointment({ appointmentId: target.dataset.id });
|
|
173
|
+
// ❌ Cannot call refreshApex — no wire result property stored
|
|
174
|
+
await refreshApex(this.appointments);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Correct — store the full wire result, call `refreshApex` after DML:**
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// appointmentList.ts
|
|
183
|
+
import { LightningElement, api, wire } from "lwc";
|
|
184
|
+
import { refreshApex } from "lightning/apex";
|
|
185
|
+
import { ShowToastEvent } from "lightning/platformShowToastEvent";
|
|
186
|
+
import getAppointments from "@salesforce/apex/AppointmentController.getAppointments";
|
|
187
|
+
import deleteAppointment from "@salesforce/apex/AppointmentController.deleteAppointment";
|
|
188
|
+
import { reduceErrors } from "c/utils";
|
|
189
|
+
|
|
190
|
+
interface AppointmentRecord {
|
|
191
|
+
Id: string;
|
|
192
|
+
Name: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type WireResult<T> = { data?: T; error?: unknown };
|
|
196
|
+
|
|
197
|
+
// @ts-ignore
|
|
198
|
+
export default class AppointmentList extends LightningElement {
|
|
199
|
+
// @ts-ignore
|
|
200
|
+
@api recordId: string;
|
|
201
|
+
|
|
202
|
+
// ✅ Store full wire result — refreshApex needs the whole object
|
|
203
|
+
wiredAppointments: WireResult<AppointmentRecord[]> = {};
|
|
204
|
+
|
|
205
|
+
public get appointments(): AppointmentRecord[] {
|
|
206
|
+
return this.wiredAppointments?.data ?? [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// @ts-ignore
|
|
210
|
+
@wire(getAppointments, { recordId: "$recordId" })
|
|
211
|
+
public handleAppointments(result: WireResult<AppointmentRecord[]>): void {
|
|
212
|
+
this.wiredAppointments = result; // ✅ keep reference for refreshApex
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public async handleDelete(event: Event): Promise<void> {
|
|
216
|
+
const target = event.target as HTMLElement;
|
|
217
|
+
try {
|
|
218
|
+
await deleteAppointment({ appointmentId: target.dataset.id });
|
|
219
|
+
await refreshApex(this.wiredAppointments); // ✅ pass the stored wire result
|
|
220
|
+
} catch (error) {
|
|
221
|
+
this.dispatchEvent(
|
|
222
|
+
new ShowToastEvent({
|
|
223
|
+
title: "Delete failed",
|
|
224
|
+
message: reduceErrors(error).join(", "),
|
|
225
|
+
variant: "error",
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Quick Decision Guide
|
|
236
|
+
|
|
237
|
+
| Situation | Use |
|
|
238
|
+
| ------------------------------------------------------------ | ------------------------------------ |
|
|
239
|
+
| Data loads when component mounts | `@wire` |
|
|
240
|
+
| Data depends on a reactive property (`$recordId`) | `@wire` |
|
|
241
|
+
| Fetch triggered by button click or form submit | Imperative |
|
|
242
|
+
| Operation has side effects (send email, run calculation) | Imperative |
|
|
243
|
+
| Apex `@wire` data stale after imperative DML | `refreshApex(this.wiredResult)` |
|
|
244
|
+
| LDS `@wire` data stale after `updateRecord` / `deleteRecord` | ❌ Not needed — LDS self-invalidates |
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
**Reference:** [Call Apex Methods — Salesforce LWC Developer Guide](https://developer.salesforce.com/docs/platform/lwc/guide/apex.html)
|