LWC Architecture & Lifecycle
📖 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:
- Web Standards — LWC uses native browser APIs (Custom Elements v1, Shadow DOM)
- Better Performance — No proprietary framework overhead; compiles to native browser code
- Modern JavaScript — ES6+ classes, modules, decorators, promises
- Smaller bundle size — Only polyfills what the browser doesn't support
- 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
1<!-- LWC Component — Account 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>1011 <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>2728 <!-- Child component communication -->29 <c-contact-list30 account-id={recordId}31 oncontactselected={handleContactSelected}>32 </c-contact-list>33 </template>3435 <template if:true={error}>36 <p class="slds-text-color_error">{error}</p>37 </template>38 </template>39 </div>4041 <div slot="actions">42 <lightning-button43 label="Edit"44 onclick={handleEdit}45 variant="brand">46 </lightning-button>47 </div>48 </lightning-card>49</template>5051// accountDetailCard.js52import { 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';5859const FIELDS = [ACCOUNT_NAME, ACCOUNT_INDUSTRY, ACCOUNT_REVENUE];6061export default class AccountDetailCard extends LightningElement {62 @api recordId; // Public property — set by parent or record page6364 // Wire adapter — reactively fetches data when recordId changes65 @wire(getRecord, { recordId: '$recordId', fields: FIELDS })66 wiredAccount;6768 // Lifecycle hooks69 connectedCallback() {70 console.log('Component connected to DOM, recordId:', this.recordId);71 }7273 renderedCallback() {74 // Careful: this runs after EVERY render75 // Use a flag to run logic only once76 }7778 disconnectedCallback() {79 // Cleanup — remove event listeners, etc.80 }8182 errorCallback(error, stack) {83 // Error boundary — catches errors from child components84 console.error('Child component error:', error.message, stack);85 }8687 // Getters (computed properties)88 get account() {89 return this.wiredAccount.data ? this.wiredAccount.data.fields : null;90 }9192 get isLoading() {93 return !this.wiredAccount.data && !this.wiredAccount.error;94 }9596 get error() {97 return this.wiredAccount.error?.body?.message;98 }99100 get cardTitle() {101 const name = this.account ? this.account.Name.value : 'Account';102 return 'Account: ' + name;103 }104105 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 }111112 // Event handlers113 handleEdit() {114 // Dispatch custom event to parent115 this.dispatchEvent(new CustomEvent('edit', {116 detail: { recordId: this.recordId }117 }));118 }119120 handleContactSelected(event) {121 // Handle event from child component122 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}130131<!-- 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:
- Build an LWC that displays Account details using the @wire decorator and getRecord
- Implement all lifecycle hooks (constructor, connectedCallback, renderedCallback, disconnectedCallback) and log when each fires
- Create a component with @api properties that accepts data from a parent
- Build a search component with debounced input and real-time results
- Create an error boundary using errorCallback that displays a friendly error message
- Implement conditional rendering with if:true/if:false and for:each loops
- Build a component with computed properties (getters) that format currency and dates
- Create a form component using lightning-input with validation
- Deploy your LWC to a Record Page using Lightning App Builder
- 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.