@api properties, and can call a child's public @api methods. A child can't change the parent's data directly, so it sends a custom event with dispatchEvent, and the parent decides what to do with it.
Real Salesforce apps are built from many small components that work together. To keep them clean and reusable, each component needs clear rules for how it talks to the others. This post covers both directions, parent to child and child to parent, using one simple example: a parent shows a product, and a child lets the user pick a quantity and add it to a cart.
1. Understanding the Parent-Child Relationship
A parent component includes one or more child components in its HTML template. The parent owns the main data and passes it down. Communication follows two simple rules:
- Downward (Parent to Child): The parent sets the child's public
@apiproperties, or calls the child's public@apimethods. - Upward (Child to Parent): The child tells the parent about user actions or changes by dispatching a
CustomEvent.
- Pass Data Down: In the child, mark properties with
@api. In the parent's HTML, set them with kebab-case attributes (camelCaseitemNamebecomesitem-name). - Call Child Methods: Mark a child method with
@api, then call it from the parent throughthis.refsorthis.template.querySelector(). - Reactivity: When the parent changes a value it passes down, the child re-renders automatically.
- Send Events Up: The child calls
this.dispatchEvent(new CustomEvent('name', { detail })). - Listen in the Parent: Add
onname={handler}on the child tag in the parent's HTML, and read the data fromevent.detail. - Unrelated Components: Use Lightning Message Service (LMS) when components don't have a parent-child relationship.
2. Parent to Child: Passing Data with @api
The child marks the properties it accepts with
@api. It also keeps its own private state, like quantity, which only the child changes.
// childComponent.js
import { LightningElement, api } from 'lwc';
export default class ChildComponent extends LightningElement {
// Public properties: set by the parent
@api itemName = 'Default Item';
@api itemPrice = 0;
// Private state: owned and changed by the child
quantity = 1;
handleQuantityChange(event) {
this.quantity = Number(event.target.value);
}
}
<!-- childComponent.html -->
<template>
<div class="slds-box slds-theme_shade">
<p>Item Name: <strong>{itemName}</strong></p>
<p>Price:
<lightning-formatted-number value={itemPrice} format-style="currency" currency-code="USD">
</lightning-formatted-number>
</p>
<lightning-input type="number" label="Quantity" min="1"
value={quantity} onchange={handleQuantityChange}>
</lightning-input>
<lightning-button class="slds-m-top_small" label="Add to Cart"
onclick={handleAddClick}>
</lightning-button>
</div>
</template>
The parent sets the child's public properties in its HTML. Remember: camelCase JavaScript names become kebab-case HTML attributes.
<!-- parentComponent.html -->
<template>
<lightning-card title="Parent Container">
<div class="slds-p-around_medium">
<c-child-component
lwc:ref="child"
item-name={productName}
item-price={productPrice}
onadditem={handleAddItem}>
</c-child-component>
<p class="slds-m-top_medium">Last added: <strong>{lastAdded}</strong></p>
<lightning-button class="slds-m-top_small" label="Reset Quantity"
onclick={handleReset}>
</lightning-button>
</div>
</lightning-card>
</template>
// parentComponent.js
import { LightningElement } from 'lwc';
export default class ParentComponent extends LightningElement {
// All fields are reactive, so @track isn't needed here
productName = 'Salesforce Enterprise License';
productPrice = 150;
lastAdded = 'Nothing yet';
// handleAddItem and handleReset are shown in the next sections
}
3. Parent to Child: Calling Public Child Methods
Sometimes a parent needs the child to do something, like clear a form or refresh its data. Mark a child method with @api, then call it from the parent.
// childComponent.js (add this method)
@api
resetQuantity() {
// Reset the child's OWN state, not its @api properties
this.quantity = 1;
}
// parentComponent.js (add this method)
handleReset() {
// lwc:ref="child" in the template gives a direct reference
this.refs.child?.resetQuantity();
// Older alternative:
// this.template.querySelector('c-child-component')?.resetQuantity();
}
lwc:ref is the modern, simpler way to get a child element. this.refs is only available after the component renders, so call it from event handlers or renderedCallback(), not from the constructor.
4. Child to Parent: Sending Custom Events
When the user clicks "Add to Cart," the child packs the data into a custom event and sends it up.
// childComponent.js (add this method)
handleAddClick() {
// Send simple, primitive values in detail
this.dispatchEvent(new CustomEvent('additem', {
detail: { itemName: this.itemName, quantity: this.quantity }
}));
}
By default, the event doesn't bubble and stays inside the child's shadow tree. Only the component that contains the child (the parent) can hear it. That's what you want for most parent-child communication.
The parent listens with
on + the event name (onadditem, already in the parent template above) and reads the data from event.detail.
// parentComponent.js (add this method)
handleAddItem(event) {
const { itemName, quantity } = event.detail;
this.lastAdded = `${quantity} x ${itemName}`;
}
5. When Components Aren't Parent and Child
- A Child Can't Call Parent Methods: The child has no reference to its parent. If the child needs the parent to act, it sends an event. That keeps the child reusable in any parent.
- Lightning Message Service (LMS): When components aren't nested, for example two components in different regions of a Lightning record page, use LMS to publish and subscribe to messages across the page.
6. Common Traps & Best Practices
Use only lowercase letters in event names, with no spaces or hyphens, and don't start them with "on." If you dispatch
new CustomEvent('itemSelect'), a parent listening with onitemselect won't catch it, because HTML attributes are always lowercase and itemselect isn't the same event as itemSelect. Name it itemselect from the start. If you need to separate words, use an underscore.
@api Values in the ChildThe parent owns the data it passes down. Objects and arrays passed through
@api are read-only in the child, and changing them throws an error. Reassigning a passed-in value also goes against the data flow, and the parent will overwrite it on its next update. If the child needs a value changed, it should send an event so the parent can change it. If the child needs an editable copy, make a copy first.
bubbles and composed to true by Defaultcomposed: true lets an event escape the shadow DOM and travel through the whole page, where any component can catch it. That makes the event part of your component's public API and can expose data you didn't mean to share. Leave both options off (the default) unless you really need the event to travel beyond the direct parent.
@api properties. Events flow up through CustomEvent. Never let a child change its parent's data directly.
- Use Kebab-Case in HTML: camelCase properties like
recordIdare written in kebab-case in markup, likerecord-id. - Keep Payloads Small: Send IDs and other primitive values in
event.detailrather than large objects. If you must send an object, send a copy so the receiver can't change the original. - Name Events by Action: Use clear names like
recordchangeorfilterapply. - Validate Incoming Data: Use a getter and setter on an
@apiproperty when the child needs to check or transform a value before using it. Put@apion only one of the pair. - Boolean Defaults: A public Boolean property must default to
false. That way, adding the attribute to the tag turns it on. - Plan First: Sketch your component tree and data flow before you build, so each component has one clear job.
Summary
Parent-child communication is a core LWC skill. Pass data down with @api properties, call child actions with @api methods and lwc:ref, and send changes back up with lowercase custom events. For components that aren't nested, use Lightning Message Service. Follow these rules and your components stay simple, reusable, and easy to maintain.
Comments
Questions, corrections and your own experience are all welcome.
Post a Comment