LWC Architecture & Lifecycle

0/4 in this phase0/41 across the roadmap

📖 Concept

Lightning Web Components (LWC) is Salesforce's modern UI framework built on web standards (Custom Elements, Shadow DOM, Templates). It replaced the proprietary Aura framework and aligns with the broader web development ecosystem.

Why LWC over Aura:

  1. Web Standards — LWC uses native browser APIs (Custom Elements v1, Shadow DOM)
  2. Better Performance — No proprietary framework overhead; compiles to native browser code
  3. Modern JavaScript — ES6+ classes, modules, decorators, promises
  4. Smaller bundle size — Only polyfills what the browser doesn't support
  5. Easier to learn — Web developers can pick it up with minimal Salesforce-specific knowledge

LWC file structure:

myComponent/
├── myComponent.html    — Template (HTML)
├── myComponent.js      — Controller (JavaScript)
├── myComponent.css     — Styles (scoped via Shadow DOM)
└── myComponent.js-meta.xml — Configuration (where the component can be used)

Component Lifecycle:

1. constructor()       — Component instance created (don't access DOM)
2. connectedCallback() — Component inserted into DOM (fetch data here)
3. renderedCallback()  — Component rendered/re-rendered (careful: runs often)
4. disconnectedCallback() — Component removed from DOM (cleanup here)
5. errorCallback(error, stack) — Error boundary (catch child errors)

Key decorators:

  • @api — Public property (parent-to-child communication)
  • @track — Tracked property (reactivity — deprecated in latest, all fields are reactive)
  • @wire — Wire adapter (reactive data fetching from Apex or LDS)

Reactivity: LWC uses a reactive system — when a property's value changes, the template re-renders automatically. All class fields are reactive by default in latest LWC. Objects and arrays need immutable update patterns (spread operator).

💻 Code Example

codeTap to expand ⛶
1<!-- LWC ComponentAccount Detail Card -->
2<!-- accountDetailCard.html -->
3<template>
4 <lightning-card title={cardTitle} icon-name="standard:account">
5 <div class="slds-p-around_medium">
6 <template if:true={isLoading}>
7 <lightning-spinner alternative-text="Loading..." size="small">
8 </lightning-spinner>
9 </template>
10
11 <template if:false={isLoading}>
12 <template if:true={account}>
13 <div class="slds-grid slds-gutters">
14 <div class="slds-col slds-size_1-of-2">
15 <p class="slds-text-heading_small">{account.Name}</p>
16 <p class="slds-text-body_regular">
17 Industry: {account.Industry}
18 </p>
19 <p class="slds-text-body_regular">
20 Revenue: {formattedRevenue}
21 </p>
22 </div>
23 <div class="slds-col slds-size_1-of-2">
24 <lightning-badge label={account.Industry}></lightning-badge>
25 </div>
26 </div>
27
28 <!-- Child component communication -->
29 <c-contact-list
30 account-id={recordId}
31 oncontactselected={handleContactSelected}>
32 </c-contact-list>
33 </template>
34
35 <template if:true={error}>
36 <p class="slds-text-color_error">{error}</p>
37 </template>
38 </template>
39 </div>
40
41 <div slot="actions">
42 <lightning-button
43 label="Edit"
44 onclick={handleEdit}
45 variant="brand">
46 </lightning-button>
47 </div>
48 </lightning-card>
49</template>
50
51// accountDetailCard.js
52import { LightningElement, api, wire } from 'lwc';
53import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
54import { ShowToastEvent } from 'lightning/platformShowToastEvent';
55import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
56import ACCOUNT_INDUSTRY from '@salesforce/schema/Account.Industry';
57import ACCOUNT_REVENUE from '@salesforce/schema/Account.AnnualRevenue';
58
59const FIELDS = [ACCOUNT_NAME, ACCOUNT_INDUSTRY, ACCOUNT_REVENUE];
60
61export default class AccountDetailCard extends LightningElement {
62 @api recordId; // Public property — set by parent or record page
63
64 // Wire adapter — reactively fetches data when recordId changes
65 @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
66 wiredAccount;
67
68 // Lifecycle hooks
69 connectedCallback() {
70 console.log('Component connected to DOM, recordId:', this.recordId);
71 }
72
73 renderedCallback() {
74 // Careful: this runs after EVERY render
75 // Use a flag to run logic only once
76 }
77
78 disconnectedCallback() {
79 // Cleanup — remove event listeners, etc.
80 }
81
82 errorCallback(error, stack) {
83 // Error boundary — catches errors from child components
84 console.error('Child component error:', error.message, stack);
85 }
86
87 // Getters (computed properties)
88 get account() {
89 return this.wiredAccount.data ? this.wiredAccount.data.fields : null;
90 }
91
92 get isLoading() {
93 return !this.wiredAccount.data && !this.wiredAccount.error;
94 }
95
96 get error() {
97 return this.wiredAccount.error?.body?.message;
98 }
99
100 get cardTitle() {
101 const name = this.account ? this.account.Name.value : 'Account';
102 return 'Account: ' + name;
103 }
104
105 get formattedRevenue() {
106 const rev = this.account?.AnnualRevenue?.value;
107 return rev ? new Intl.NumberFormat('en-US', {
108 style: 'currency', currency: 'USD'
109 }).format(rev) : 'N/A';
110 }
111
112 // Event handlers
113 handleEdit() {
114 // Dispatch custom event to parent
115 this.dispatchEvent(new CustomEvent('edit', {
116 detail: { recordId: this.recordId }
117 }));
118 }
119
120 handleContactSelected(event) {
121 // Handle event from child component
122 const contactId = event.detail.contactId;
123 this.dispatchEvent(new ShowToastEvent({
124 title: 'Contact Selected',
125 message: 'Selected contact: ' + contactId,
126 variant: 'success'
127 }));
128 }
129}
130
131<!-- accountDetailCard.js-meta.xml -->
132<?xml version="1.0" encoding="UTF-8"?>
133<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
134 <apiVersion>59.0</apiVersion>
135 <isExposed>true</isExposed>
136 <targets>
137 <target>lightning__RecordPage</target>
138 <target>lightning__AppPage</target>
139 <target>lightning__HomePage</target>
140 </targets>
141 <targetConfigs>
142 <targetConfig targets="lightning__RecordPage">
143 <objects>
144 <object>Account</object>
145 </objects>
146 </targetConfig>
147 </targetConfigs>
148</LightningComponentBundle>

🏋️ Practice Exercise

LWC Fundamentals Practice:

  1. Build an LWC that displays Account details using the @wire decorator and getRecord
  2. Implement all lifecycle hooks (constructor, connectedCallback, renderedCallback, disconnectedCallback) and log when each fires
  3. Create a component with @api properties that accepts data from a parent
  4. Build a search component with debounced input and real-time results
  5. Create an error boundary using errorCallback that displays a friendly error message
  6. Implement conditional rendering with if:true/if:false and for:each loops
  7. Build a component with computed properties (getters) that format currency and dates
  8. Create a form component using lightning-input with validation
  9. Deploy your LWC to a Record Page using Lightning App Builder
  10. Write Jest tests for your LWC component

⚠️ Common Mistakes

  • Accessing DOM in constructor() — the DOM isn't available yet. Use connectedCallback() or renderedCallback()

  • Heavy operations in renderedCallback() — it runs after EVERY render. Use a boolean flag to run initialization logic only once

  • Forgetting that @api properties are read-only inside the component — you can't reassign them. Copy to a local variable if you need to modify

  • Mutating objects/arrays directly instead of using spread — reactive updates require new object references: this.items = [...this.items, newItem]

  • Not setting isExposed=true in the meta XML — the component won't appear in Lightning App Builder without it

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for LWC Architecture & Lifecycle. Login to unlock this feature.