🏠 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 →

LWC Zero to Hero Module 2 — Component Anatomy & Lifecycle Hooks

LWC Zero to Hero - Module 2: Component Anatomy & Lifecycle Hooks | SF Interview Pro
🧩 LWC Zero to Hero — Module 2 of 15

Component Anatomy
& Lifecycle Hooks

Every LWC has 3 files and a predictable "life story" from creation to removal. Master this structure and you'll never be confused about WHERE to put code or WHEN it runs again.

Module 2 of 15 (counting Module 0 as a bonus prerequisite) · Phase 2: LWC Fundamentals
🎯 What You'll Master in This Module
Now that you know modern JavaScript (Module 1), it's time to see how that JavaScript actually becomes a working Lightning Web Component. Every LWC follows the exact same structural pattern and the exact same lifecycle sequence — once you internalize this, reading ANY component's code (yours or someone else's) becomes instantly easier.
The 3 core files every component has, and what each one is responsible for
How the HTML template connects to your JavaScript
The role of LightningElement and what your class inherits from it
How meta.xml controls where your component can be used
The complete lifecycle hook sequence, in the exact order it fires
constructor() vs connectedCallback() — when to use which
renderedCallback() and disconnectedCallback() — render-time and cleanup logic
Concept 1 of 7
The Three Core Files Every LWC Has
Every Lightning Web Component bundle consists of (at minimum) 3 files, all sharing the same name in the same folder: a .html file (the template/structure), a .js file (the logic/behavior), and a .js-meta.xml file (the configuration). Salesforce automatically connects these three based on the matching filename — there's no manual "wiring" step.
⚡ Why This Matters
When you create a component called orderCard, you'll see orderCard.html, orderCard.js, and orderCard.js-meta.xml in the same folder. If you ever wonder "where does this piece of logic go," the answer is always one of these three files — structure goes in HTML, behavior goes in JS, configuration goes in meta.xml.
FOLDER STRUCTURE — orderCard component bundle
orderCard/
├── orderCard.html          // Template — structure & layout
├── orderCard.js             // Class — logic, data, event handlers
├── orderCard.js-meta.xml    // Config — where it can be used, API version
├── orderCard.css            // Optional — component-specific styling
└── __tests__/                // Optional — Jest test files (Module 14)
    └── orderCard.test.js
HOW THEY CONNECT — minimal example of all three
<!-- orderCard.html -->
<template>
  <div>Order: {orderName}</div>
</template>

// orderCard.js
import { LightningElement } from 'lwc';
export default class OrderCard extends LightningElement {
  orderName = 'Order #1001';  // the {orderName} in HTML reads this
}

<!-- orderCard.js-meta.xml -->
<LightningComponentBundle>
  <apiVersion>62.0</apiVersion>
  <isExposed>true</isExposed>
</LightningComponentBundle>
🛠️ Practical Mini-Implementation
Notice the connection: {orderName} in the HTML automatically displays whatever value orderName holds in the JS class. This is the most fundamental LWC concept — the template reads from the class's properties, and Salesforce keeps them in sync automatically (the reactivity system from Module 1's spread/rest discussion).
⚠️ Common Gotcha
All file names within a bundle MUST match exactly (case-sensitive) and use camelCase for the folder/file name (e.g., orderCard, not OrderCard or order-card). If you rename files inconsistently, Salesforce won't connect them properly, and you'll get confusing "component not found" deployment errors.
Concept 2 of 7
The HTML Template — Structure & Expressions
Every LWC HTML file must have exactly ONE root <template> tag wrapping everything else. Inside it, you write mostly normal HTML, plus special LWC syntax: expressions ({propertyName}) to display dynamic values, and directives (covered fully in Module 3) for conditionals and loops.
⚡ Why This Matters
The template is "read-only" from the data's perspective — you can DISPLAY values using { } expressions, but you cannot write JavaScript logic (no if-statements, no function calls with arguments) directly inside the HTML. All logic lives in the JS class; the template just displays the results.
BASIC TEMPLATE STRUCTURE
<template>  <!-- exactly ONE root template tag -->
  <div class="order-card">
    <h2>{orderName}</h2          <!-- expression: shows orderName's value -->
    <p>Total: ${orderTotal}</p>    <!-- you CAN mix expressions with text -->
    <p>Status: {statusLabel}</p> <!-- statusLabel could be a getter! -->
  </div>
</template>
EXPRESSIONS CAN REFERENCE GETTERS, NOT JUST FIELDS
// orderCard.js
export default class OrderCard extends LightningElement {
  orderTotal = 1500;

  get statusLabel() {
    return this.orderTotal > 1000 ? 'High Value' : 'Standard';
  }
}
// {statusLabel} in the template calls this getter and displays "High Value"
🛠️ Practical Mini-Implementation
A handler reference in HTML looks like an expression but actually wires up a JS function to an event:

<button onclick={handleClick}>Submit</button>

Notice: NO parentheses after handleClick (you're not calling it, you're just telling LWC "this is the function to call WHEN clicked"). Writing onclick={handleClick()} (with parentheses) is a common beginner mistake that calls the function immediately during render instead of on click.
⚠️ Common Gotcha
You CANNOT write JavaScript expressions with operators or function calls directly inside { }{orderTotal * 2} or {getStatus()} will NOT work. Expressions can only reference a simple property or getter NAME. Any computation must happen in a getter in your JS class first, then be referenced by name in the template.
Concept 3 of 7
The JavaScript Class & LightningElement
Recall from Module 1 (Concept 6) that classes can extend other classes to inherit their capabilities. Every LWC component class extends LightningElement — a built-in base class that Salesforce provides, giving your component access to the DOM (this.template), lifecycle hooks, event dispatching, and more.
⚡ Why This Matters
LightningElement is what turns a "plain JavaScript class" into something Salesforce recognizes and renders as a Web Component. Without extends LightningElement, your file is just JavaScript with no connection to the page.
WHAT extends LightningElement GIVES YOU
import { LightningElement } from 'lwc';

export default class OrderCard extends LightningElement {

  connectedCallback() {            // lifecycle hook — inherited from LightningElement
    console.log('Component added to page');
  }

  handleClick() {
    this.dispatchEvent(                // dispatchEvent — inherited method
      new CustomEvent('orderselected')
    );

    const el = this.template.querySelector('div'); // this.template — inherited property
  }
}
DECORATORS — special markers that modify class fields/methods
import { LightningElement, api, track, wire } from 'lwc';

export default class OrderCard extends LightningElement {
  @api orderId;        // makes this PUBLIC — parent components can set it
  @track items = [];   // explicitly marks for deep reactivity tracking

  @wire(getOrder, { orderId: '$orderId' })  // wire decorator — covered fully in Module 12
  wiredOrder;
}
// Decorators are previewed here; @api gets a full deep-dive in Module 2.5/3
🛠️ Practical Mini-Implementation
A common early mistake is forgetting export default before the class — every component file needs exactly this pattern: export default class ComponentName extends LightningElement { }. Missing export default means Salesforce can't find your component class at all when trying to render it.
⚠️ Common Gotcha
The class name inside the JS file (e.g., OrderCard) is conventionally PascalCase (capital first letter), while the FILE/FOLDER name is camelCase (orderCard). This mismatch is intentional and correct — don't "fix" it by renaming the class to lowercase, that will cause errors.
Concept 4 of 7
The meta.xml Configuration File
The .js-meta.xml file doesn't contain any logic — it's pure configuration that tells Salesforce WHERE your component is allowed to be used (App Builder pages, record pages, Experience Cloud sites, etc.) and WHAT version of the platform API it targets.
⚡ Why This Matters
A perfectly working component is USELESS to admins if isExposed is false or missing — it simply won't appear as an option in Lightning App Builder. This is one of the most common "why can't I find my component" support questions for beginners.
MINIMAL meta.xml — required for App Builder visibility
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>   <!-- without this, it's invisible to App Builder -->
</LightningComponentBundle>
SPECIFYING WHERE THE COMPONENT CAN BE USED (targets)
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>   <!-- record pages -->
        <target>lightning__AppPage</target>      <!-- Lightning app pages -->
        <target>lightning__HomePage</target>     <!-- home page -->
        <target>lightningCommunity__Page</target> <!-- Experience Cloud -->
    </targets>
</LightningComponentBundle>
🛠️ Practical Mini-Implementation
If your component needs configurable properties when an admin drags it onto a page (like a "Number of Records to Show" field), that configuration UI is also defined inside the <targetConfigs> section of this same file — you'll build this hands-on in Module 8 (@api Properties).
⚠️ Common Gotcha
If a component is meant to be used as a CHILD inside another component (not directly placed by an admin), you typically do NOT need isExposed = true or any targets at all — it just needs to exist in the org. Setting isExposed = true on every component "just in case" is unnecessary and clutters the App Builder component list for admins.
Concept 5 of 7
Lifecycle Hooks — The Full Sequence
Every component goes through a predictable "life story" from the moment it's created to the moment it's removed from the page. Lifecycle hooks are special methods that automatically fire at each stage — you don't call them yourself, LWC calls them FOR you at the right moment.
⚡ Why This Matters
"Where do I put my data-fetching code?" "Why is this DOM element not found yet?" "How do I clean up before the component disappears?" — every one of these common questions is answered by knowing exactly WHEN each lifecycle hook fires.
🔄 The Complete Sequence (Single Component, First Render)
1constructor() — component instance is created (rarely used directly)
2connectedCallback() — component is inserted into the DOM (page)
3render() — internal — LWC renders the template (not a hook you write)
4renderedCallback() — fires AFTER the template has rendered to the DOM
5... component lives on screen, re-renders if reactive data changes (renderedCallback fires again each time) ...
6disconnectedCallback() — fires when the component is removed from the page
SEEING THE SEQUENCE WITH console.log
export default class LifecycleDemo extends LightningElement {
  constructor() {
    super();  // always required first in a constructor
    console.log('1. constructor');
  }
  connectedCallback() {
    console.log('2. connectedCallback');
  }
  renderedCallback() {
    console.log('3. renderedCallback');
  }
  disconnectedCallback() {
    console.log('4. disconnectedCallback');
  }
}
// Console output on page load: 1. constructor → 2. connectedCallback → 3. renderedCallback
// disconnectedCallback only fires later, when removed
🛠️ Practical Mini-Implementation
A simple mental model that works for 90% of cases: fetch data in connectedCallback(), interact with rendered DOM elements in renderedCallback(), and clean up subscriptions/timers in disconnectedCallback(). We'll go deeper into each in Concepts 6 and 7.
⚠️ Common Gotcha
For a PARENT component with CHILD components inside it, the order is: parent's constructor → parent's connectedCallback → child's constructor → child's connectedCallback → child's renderedCallback → parent's renderedCallback. Children connect "inside-out" but render "bottom-up" — this trips people up when debugging nested component issues.
Concept 6 of 7
constructor() & connectedCallback() — Birth of a Component
constructor() fires first, the moment the class instance is created — but at this point, the component ISN'T on the page yet, and crucially, @api property values from the parent haven't been set yet either. connectedCallback() fires next, once the component is actually inserted into the DOM — this is where most "setup" logic belongs.
⚡ Why This Matters
This is THE most common beginner mistake in all of LWC: trying to read an @api property's value inside constructor() and getting undefined. The fix is always the same — move that logic to connectedCallback().
❌ THE CLASSIC MISTAKE
export default class OrderDetail extends LightningElement {
  @api recordId;

  constructor() {
    super();
    console.log(this.recordId); // undefined! Parent hasn't set it yet
  }
}
✅ THE FIX — use connectedCallback()
export default class OrderDetail extends LightningElement {
  @api recordId;

  connectedCallback() {
    console.log(this.recordId); // ✅ correct value, e.g. "001xx000003DGb2AAA"
    this.fetchOrderData();             // fetch data using the recordId here
  }

  async fetchOrderData() {
    const result = await getOrderById({ orderId: this.recordId });
    this.orderData = result;
  }
}
🛠️ Practical Mini-Implementation
What IS appropriate inside constructor()? Initializing simple internal default values that don't depend on @api properties, or calling super() (mandatory, must be the first line if you define a constructor at all). In practice, most LWC components never need a custom constructor() — you'll use connectedCallback() far more often.
⚠️ Common Gotcha
If you DO write a constructor(), you MUST call super(); as the very first line, before accessing this at all. Forgetting super() throws a runtime error immediately. Most developers skip writing a constructor entirely and just use connectedCallback() for setup logic to avoid this rule altogether.
Concept 7 of 7
renderedCallback() & disconnectedCallback()
renderedCallback() fires every single time the component finishes rendering (or re-rendering) — including the FIRST render AND every subsequent re-render whenever reactive data changes. disconnectedCallback() fires once, right before the component is removed from the page entirely — your last chance to clean up.
⚡ Why This Matters
If you need to interact with an actual DOM element (measure its size, focus an input, integrate a third-party JS library), the element must already be RENDERED — which is exactly what renderedCallback() guarantees. And any timers, event listeners, or subscriptions you set up need to be torn down in disconnectedCallback() or they'll cause memory leaks.
renderedCallback() — accessing rendered DOM elements
export default class SearchBox extends LightningElement {
  hasFocused = false;

  renderedCallback() {
    if (!this.hasFocused) {
      const input = this.template.querySelector('input');
      if (input) {
        input.focus();                // safe — element definitely exists now
        this.hasFocused = true;  // guard prevents re-focusing on every re-render
      }
    }
  }
}
disconnectedCallback() — cleanup before removal
export default class LiveOrderTracker extends LightningElement {
  intervalId;

  connectedCallback() {
    // set up a recurring timer when component appears
    this.intervalId = setInterval(() => {
      this.refreshOrderStatus();
    }, 5000);
  }

  disconnectedCallback() {
    // MUST clear the timer, or it keeps running after component is gone!
    clearInterval(this.intervalId);
  }
}
🛠️ Practical Mini-Implementation
The "guard flag" pattern (hasFocused in the example above) is essential — without it, code inside renderedCallback() runs on EVERY re-render, which can cause repeated, unwanted actions (re-focusing an input every time unrelated data changes, for example). Always ask "should this really run every single render, or just once?"
⚠️ Common Gotcha
Forgetting to clean up in disconnectedCallback() — especially setInterval/setTimeout timers, manually added event listeners, or Lightning Message Service subscriptions (Module 10) — is one of the most common sources of subtle bugs and memory leaks in production LWC code. If you set something up in connectedCallback(), ask yourself "does this need to be torn down?"
💬 Module 2 Interview Questions (6)
Q1What are the three core files in an LWC bundle and what is each one responsible for?
Every LWC bundle has: (1) a .html file containing the template/markup — the structure and layout using expressions and directives; (2) a .js file containing the component's class, which extends LightningElement and holds all logic, data, and event handlers; and (3) a .js-meta.xml file containing configuration metadata — API version, exposure settings (isExposed), and target locations (record pages, app pages, Experience Cloud) where the component can be placed. All three files share the same camelCase name and Salesforce connects them automatically based on that shared name.
"The HTML file defines structure, the JS file (extending LightningElement) defines behavior and data, and the meta.xml file defines configuration like exposure and target pages — all three share the same filename."
Q2Why can't you write {orderTotal * 2} directly in an LWC HTML template?
LWC template expressions using { } can only reference a simple identifier — a property name or a getter name — not an inline JavaScript expression with operators or function calls. This is a deliberate design constraint that keeps logic in the JavaScript class (where it belongs, and where it's testable) rather than scattering computation across the template. The correct approach is to create a getter in the JS class, like get doubledTotal() { return this.orderTotal * 2; }, and reference {doubledTotal} in the template instead.
"LWC template expressions only support simple property/getter names, not inline computation — any calculation must happen in a JavaScript getter, which the template then references by name."
Q3Why does isExposed need to be set to true in meta.xml, and what happens if you forget it?
isExposed controls whether the component appears as a selectable option for admins in Lightning App Builder, Experience Builder, or other declarative tools. If it's missing or set to false, the component still technically EXISTS and could be used as a child component referenced by another component's HTML, but it will NOT appear in any drag-and-drop builder interface. This commonly confuses beginners who build a component, deploy it successfully with no errors, but then can't find it when trying to add it to a Lightning page via App Builder.
"isExposed=true makes a component selectable in declarative tools like App Builder; without it, the component deploys fine but stays invisible to admins trying to place it on a page."
Q4Explain the order of lifecycle hooks for a single component on its first render, and why constructor() should not be used to read @api property values.
The order is: constructor() → connectedCallback() → render (internal) → renderedCallback(). The key issue is timing: when constructor() runs, the component instance has just been created, but the parent component has not yet had the opportunity to set @api property values onto the child. Reading an @api property inside constructor() will return undefined. By the time connectedCallback() fires (the component has been inserted into the DOM), all @api properties have been properly set by the parent, making it the correct place for setup logic that depends on those values, like fetching data based on a recordId.
"The sequence is constructor → connectedCallback → renderedCallback; @api properties aren't populated yet during constructor(), so data-fetching and setup logic that depends on them belongs in connectedCallback() instead."
Q5How many times does renderedCallback() fire during a component's lifetime, and why might this cause unexpected bugs?
renderedCallback() fires after EVERY render — the initial render AND every subsequent re-render triggered by any reactive data change. This is different from connectedCallback() (fires once) and disconnectedCallback() (fires once). The bug risk: if you put code inside renderedCallback() that performs an action like focusing an input or initializing a third-party library, that action will repeat on every single re-render unless you add a guard condition (like a boolean flag checked before running the logic) to ensure it only happens once or only under specific circumstances.
"renderedCallback() fires on every render, not just the first one, so any one-time setup logic inside it needs a guard flag to prevent it from re-running unnecessarily on every subsequent re-render."
Q6You set up a setInterval() timer in connectedCallback() to poll for data every 5 seconds. What must you also do, and where, to avoid a memory leak?
You must clear the interval using clearInterval() inside disconnectedCallback(), passing the interval ID that was returned when setInterval() was originally called. Without this cleanup, when the component is removed from the page (user navigates away, component is conditionally hidden, etc.), the timer keeps running in the background indefinitely, continuing to execute its callback function and consume memory/resources even though the component itself no longer exists on screen. This is one of the most common sources of memory leaks and unexpected behavior in production LWC applications.
"Any setInterval, setTimeout, event listener, or subscription created in connectedCallback() must be torn down in disconnectedCallback() — otherwise it keeps running after the component is removed, causing a memory leak."
📝 Module 2 Recap — Component Anatomy Mastered
✅ Every LWC has 3 core files (HTML, JS, meta.xml) sharing the same camelCase name
✅ The HTML template uses {expressions} to display values from the JS class — no inline computation allowed
✅ Every component class extends LightningElement, inheriting lifecycle hooks, this.template, and dispatchEvent()
✅ meta.xml's isExposed and targets control where admins can place your component declaratively
✅ The lifecycle sequence is: constructor → connectedCallback → renderedCallback (repeating) → disconnectedCallback
✅ Fetch data depending on @api properties in connectedCallback(), NOT constructor()
✅ Use renderedCallback() (with guard flags) for DOM interaction, and ALWAYS clean up timers/listeners in disconnectedCallback()
🎯 Before Moving to Module 3...
Try this: create a simple component with all 4 lifecycle hooks, each logging a message with console.log. Deploy it to a record page, open the browser console, and watch the exact order they fire. Then navigate away from that page and confirm disconnectedCallback() fires. Seeing this sequence with your own eyes makes it permanent knowledge. Module 3 builds directly on the template skills from Concept 2 — diving into directives: lwc:if, for:each, and conditional rendering patterns.
☕ 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 QR Code to support sfinterviewpro
rajnishrk264@ybl
Scan with GPay, PhonePe, Paytm, or BHIM