Intermediate 30 min read

Project: Components That Talk to Each Other

By the end of this lesson, you'll be able to:

  • Combine @api and CustomEvent to build a working parent/child component set
  • Wire a list component and a detail component together through a shared parent
  • Apply the immutable update pattern to keep the whole system correctly reactive

Prerequisites: "Sibling Communication with Lightning Message Service"

What We're Building

Three components working together:

  • contactManager — the parent, holding the list of contacts and the currently selected one.
  • contactList — a child that receives the full contact array via @api and dispatches a select event when a row is clicked.
  • contactDetail — a child that receives the selected contact via @api and displays it.

Building contactList.js

import { LightningElement, api } from 'lwc';

export default class ContactList extends LightningElement {
    @api contacts;

    handleRowClick(event) {
        const selectedId = event.currentTarget.dataset.id;
        this.dispatchEvent(new CustomEvent('select', { detail: selectedId }));
    }
}
<template>
    <template for:each={contacts} for:item="contact">
        <p key={contact.Id} data-id={contact.Id} onclick={handleRowClick}>
            {contact.Name}
        </p>
    </template>
</template>

Building contactDetail.js

import { LightningElement, api } from 'lwc';

export default class ContactDetail extends LightningElement {
    @api contact;
}
<template>
    <template if:true={contact}>
        <p>{contact.Name}</p>
        <p>{contact.Email}</p>
    </template>
    <template if:false={contact}>
        <p>Select a contact to see details.</p>
    </template>
</template>

Wiring It Together in contactManager.js

import { LightningElement } from 'lwc';

export default class ContactManager extends LightningElement {
    contacts = [
        { Id: '1', Name: 'Ada Lovelace', Email: 'ada@example.com' },
        { Id: '2', Name: 'Grace Hopper', Email: 'grace@example.com' },
    ];
    selectedContact;

    handleSelect(event) {
        const selectedId = event.detail;
        this.selectedContact = this.contacts.find(c => c.Id === selectedId);
    }
}
<template>
    <c-contact-list contacts={contacts} onselect={handleSelect}></c-contact-list>
    <c-contact-detail contact={selectedContact}></c-contact-detail>
</template>

Data flows down from the parent to contactList via @api contacts, a click fires a select CustomEvent back up, the parent finds the matching record and passes it down to contactDetail — the complete loop this whole module has been building toward.

Exercise

Trace the data flow, as a comment: when a user clicks a contact row, list every step from that click to contactDetail showing the right contact.

Show hint

Follow the event up, then the data back down.

JAVASCRIPT

Exercise

Challenge: extend contactManager so contacts is initially empty and a "Load Contacts" button populates it. Describe, without full code, what changes and why the update remains reactive.

Show hint

Think about how the array gets replaced, not mutated.

JAVASCRIPT

Project: Components That Talk to Each Other Quiz

1. In this project, how does data flow from contactManager down to contactList?

2. How does contactList notify contactManager that a row was clicked?

3. contactDetail receives its data through Lightning Message Service in this project.

4. What finds the correct contact record after a selection event is received?

5. Why does reassigning this.selectedContact (rather than mutating it) matter here?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A hands-on capstone combining everything this module covers: a Contact Manager parent coordinating a Contact List child (passing data down) and a Contact Detail child (receiving a selection back up).