lwc:if to conditionally show or hide elements, for:each to loop through data (always providing a unique key), and {property} syntax to bind data from your JavaScript class. Rendering in LWC is strictly one-way: when the JavaScript data changes, the screen automatically updates. You should almost never manipulate the DOM manually.
Using
lwc:if={isLoading} displays a loading spinner while data fetches. When looping over a list of support cases using for:each, you must use key={case.Id}. Never use the loop index as the key. If you do, reordering the list will force the browser to rebuild the entire DOM, causing you to lose user focus and destroy performance.
๐️ Core Concept: Anatomy of an LWC
A Lightning Web Component is fundamentally composed of three files: an HTML template, a JavaScript class, and a metadata XML configuration file.
- The template is standard HTML enhanced with a minimal set of LWC directives.
- Rendering is completely reactive. When a reactive JavaScript property changes, LWC automatically diffs the DOM and re-renders only the parts of the template that changed.
- Data binding is strictly ONE-WAY by default.
- The JavaScript class pushes values into the template using
{property}syntax, or utilizes agetterto compute derived values dynamically. - The template never writes data back to the JavaScript class automatically.
- What developers often refer to as "two-way binding" is actually a manual combination of one-way rendering and an event handler (like
onchangeoroninput) that readsevent.target.valueand assigns it back to the JavaScript property. - Visual logic is controlled via directives, not imperative DOM manipulation:
lwc:iffor visibility,for:eachfor iteration, andslotfor composing markup between components. - Every component's DOM is isolated inside its own Shadow Boundary. You must use
this.template.querySelectorto interact with elements inside your specific component.
๐งญ 360 Card — Template & Rendering
Rule: Data binding flows in one direction: from the JavaScript class down to the HTML template. You must manually write the value back using an event handler to achieve a two-way update.
Gain: One-way data flow guarantees that every change on the UI has a single, traceable cause, making debugging significantly easier.
Price: "Two-way" binding requires manual assembly: render the data down, catch the onchange event, and assign the new value back to the property.
Limits: Use lwc:if, lwc:elseif, and lwc:else instead of the legacy if:true and if:false. The for:each directive absolutely requires a stable key (never an array index). Logical comparisons belong in a JS getter, never directly in the HTML template.
Mirror (Two-Way Binding Frameworks): Frameworks with true two-way binding (like Angular's ng-model) require less code but can create tangled, untraceable state changes as the application scales.
At Volume: If you use an array index as a key on a 1,000-row list, a simple column sort will force the browser to destroy and rebuild all 1,000 DOM nodes. Always use a unique record ID to preserve performance.
❓ Core Q&A: Rendering & State
Q: Is data binding in LWC one-way or two-way? How do you implement a form field that updates the component’s state?
A: Binding is one-way—from the JS class to the template. If this.name changes in JS, the template re-renders. However, a user typing into an input field does NOT automatically update this.name.
To capture user input, you wire an event handler:
// 1. Data flows DOWN as a property. Events flow UP.
handleChange(event) {
this.name = event.target.value; // Explicitly assign value back
}
<!-- HTML Template -->
<lightning-input value={name} onchange={handleChange}></lightning-input>
LWC intentionally omits automatic two-way binding to keep data flow predictable and explicit. Furthermore, for derived or combined values (like full name), do not create a second variable. Expose a JavaScript getter instead, which recalculates automatically on render.
LWC is ONE-WAY: class → template. "Two-way binding" is an illusion created by combining downward rendering with an
onchange handler writing back event.target.value. There is no ng-model equivalent here.
๐ก Scenario Follow-Ups
Q1: How do you conditionally render markup, and what changed in the syntax recently?
A1: The legacy approach used <template if:true={isOpen}> and <template if:false={isOpen}>. As of the Spring '23 release, LWC introduced a true conditional chain using lwc:if, lwc:elseif, and lwc:else.
- You no longer need to pair
if:trueandif:falseacross separate template tags. - Only the matched branch renders, improving performance.
- Crucial: You cannot put logical expressions (like
{a > b}) directly in the HTML. The comparison must be evaluated in a JavaScript getter, which returns a simple boolean to the template.
Q2: Explain for:each versus iterator. Why is the key attribute mandatory?
A2: for:each={items} is the standard way to loop over arrays. However, iterator:it={items} is a specialized loop that exposes it.first and it.last. You use iterator when the very first or last item in a list requires distinct HTML markup (like removing a top border on the first row).
In both scenarios, providing a key={...} is mandatory on the first element inside the loop. The key must be a unique, stable string or number (like a Salesforce Record ID). Never use the array index. LWC uses the key to map DOM nodes to data items; if the list is reordered and the index is used as a key, the framework loses track and destroys/rebuilds the entire DOM subtree, causing massive performance drops and wiping out user input focus.
Q3: What are "slots," and how does markup from a parent end up inside a child component?
A3: A <slot> is a designated placeholder inside a CHILD component's HTML template that the PARENT component is allowed to fill with its own custom markup.
- An unnamed
<slot></slot>is the default destination for injected markup. - A named slot (
<slot name="header">) captures specific markup passed from the parent using theslot="header"attribute. - This architecture is how you build reusable container UI (like cards or modals) where the outer structural shell is fixed, but the inner content is dynamic.
- Trap: The slotted content strictly belongs to the PARENT. Any data bindings or event listeners on the slotted markup are controlled by the parent's JavaScript, not the child's.
Q4: How do you properly compose components, pass data down, and interact with the DOM?
A4: Composition means embedding one component inside another.
- A child component folder named
childComponentis referenced in the parent HTML using kebab-case and thec-namespace:<c-child-component></c-child-component>. - Data flows downward through properties decorated with
@apiin the child. In JavaScript, it is camelCase (@api recordId). In HTML, it converts to kebab-case (record-id={myId}). - To manipulate your own DOM, you must use
this.template.querySelector(). Standarddocument.querySelector()will fail because it cannot pierce the Shadow DOM boundary. - You cannot use
querySelectorto reach into a child component's internal HTML. If you need a child to change, call the child's exposed@apimethods.