LWC Ex 2: Event Propagation with Component Hierarchy

Create 5 Lightning web components in VS Code called childOne, childTwo, childThree, parentOne and grandparentOne – this will generate the html and js files. You can use the below code to try out the solution and customize it further if you need to.

Disclaimer – please note that this is just one way to address the requirement – there could be more and better ways to solve this. The code sample is only to give you an idea – feel free to try more innovative ways of solving this challenge.

childOne.html

<template>
    <div class="slds-box">
        <lightning-card title="Child One" icon-name="standard:people">
            <div class="slds-align_absolute-center">
                <lightning-button
                    variant={btnBrand}
                    label={btnSelection}
                    onclick={handleClick}
                ></lightning-button>
            </div>
        </lightning-card>  
        <p></p>
    </div>
</template>

childOne.js

import { LightningElement, api } from 'lwc';

export default class ChildOne extends LightningElement {
    @api btnSelection = "Select";
    btnBrand = "success";
    @api childName = "ChildOne";

    @api childReset(){
        this.btnSelection = "Select";
        this.btnBrand = "success";
    }

    handleClick(event){
        const event1 = new CustomEvent('childclick',
        {
            bubbles: true,
            composed: true,
            detail: this.btnSelection
        });
        this.dispatchEvent(event1);

        if (this.btnSelection === "Select"){
            this.btnSelection = "Deselect";
            this.btnBrand = "destructive";
        }
        else{
            this.btnSelection = "Select";
            this.btnBrand = "success";
        }
    }
}

childTwo.html

<template>
    <div class="slds-box">
        <lightning-card title="Child Two" icon-name="standard:people">
            <div class="slds-align_absolute-center">
                <lightning-button
                    variant={btnBrand}
                    label={btnSelection}
                    onclick={handleClick}
                ></lightning-button>
            </div>
        </lightning-card>  
        <p></p>
    </div>
</template>

childTwo.js

import { LightningElement, api } from 'lwc';

export default class ChildTwo extends LightningElement {
    @api btnSelection = "Select";
    btnBrand = "success";
    @api childName = "ChildTwo";

    @api childReset(){
        this.btnSelection = "Select";
        this.btnBrand = "success";
    }

    handleClick(event){
        const event1 = new CustomEvent('childclick',
        {
            bubbles: true,
            composed: true,
            detail: this.btnSelection
        });
        this.dispatchEvent(event1);

        if (this.btnSelection === "Select"){
            this.btnSelection = "Deselect";
            this.btnBrand = "destructive";
        }
        else{
            this.btnSelection = "Select";
            this.btnBrand = "success";
        }
    }
}

childThree.html

<template>
    <div class="slds-box">
        <lightning-card title="Child Three" icon-name="standard:people">
            <div class="slds-align_absolute-center">
                <lightning-button
                    variant={btnBrand}
                    label={btnSelection}
                    onclick={handleClick}
                ></lightning-button>
            </div>
        </lightning-card>  
        <p></p>
    </div>
</template>

childThree.js

import { LightningElement, api } from 'lwc';

export default class ChildThree extends LightningElement {
    @api btnSelection = "Select";
    btnBrand = "success";
    @api childName = "ChildThree";

    @api childReset(){
        this.btnSelection = "Select";
        this.btnBrand = "success";
    }

    handleClick(event){
        const event1 = new CustomEvent('childclick',
        {
            bubbles: true,
            composed: true,
            detail: this.btnSelection
        });
        this.dispatchEvent(event1);

        if (this.btnSelection === "Select"){
            this.btnSelection = "Deselect";
            this.btnBrand = "destructive";
        }
        else{
            this.btnSelection = "Select";
            this.btnBrand = "success";
        }
    }
}

parentOne.html

<template>
    <div class="slds-box">
        <lightning-card title="Parent" icon-name="standard:person_account">
            <div class="slds-align_absolute-center" onchildclick={handleChild}>
                <table>
                    <tr>
                        <td>Child One: {childOneSel}</td>
                        <td>Child Two: {childTwoSel}</td>
                        <td>Child Three: {childThreeSel}</td>
                    </tr>
                    <tr>
                        <td>&nbsp;</td>
                        <td>&nbsp;</td>
                        <td>&nbsp;</td>
                    </tr>
                    <tr>
                        <td><c-child-one></c-child-one></td>
                        <td><c-child-two></c-child-two></td>
                        <td><c-child-three></c-child-three></td>
                    </tr>
                </table>
            </div>
        </lightning-card>  
    </div>
</template>

parentOne.js

import { LightningElement, api } from 'lwc';

export default class ParentOne extends LightningElement {
    childOneSel = "Deselected";
    childTwoSel = "Deselected";
    childThreeSel = "Deselected";

    @api parentReset(){
        this.childOneSel = "Deselected";
        this.childTwoSel = "Deselected";
        this.childThreeSel = "Deselected";

        this.template.querySelector('c-child-one').childReset();
        this.template.querySelector('c-child-two').childReset();
        this.template.querySelector('c-child-three').childReset();
    }

    handleChild(event){
        switch(event.target.childName){
            case "ChildOne":
                event.target.btnSelection === "Select" ? this.childOneSel = "Selected" : this.childOneSel = "Deselected";
                break;
            case "ChildTwo":
                event.target.btnSelection === "Select" ? this.childTwoSel = "Selected" : this.childTwoSel = "Deselected";
                break;
            case "ChildThree":
                event.target.btnSelection === "Select" ? this.childThreeSel = "Selected" : this.childThreeSel = "Deselected";
                break;
        }
    }
}

grandparentOne.html

<template>
    <div class="slds-box">
        <lightning-card title="Grand Parent" icon-name="standard:omni_supervisor">
            <div class="slds-align_absolute-center" onchildclick={handleChild}>
                <table>
                    <tr class="slds-table">
                        <td>{numChildSel} of 3 Children selected</td>
                        <td>
                            <lightning-button
                                variant="brand"
                                label="Reset All"
                                onclick={handleReset}>
                            </lightning-button>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="2"><c-parent-one></c-parent-one></td>
                    </tr>
                </table>
            </div>
        </lightning-card>  
    </div>
</template>

grandparentOne.js

import { LightningElement } from 'lwc';

export default class GrandparentOne extends LightningElement {
    numChildSel = 0;

    handleChild(event){
        event.detail === "Select" ? this.numChildSel += 1 : this.numChildSel -= 1;
    }

    handleReset(event){
        this.numChildSel = 0;
        this.template.querySelector('c-parent-one').parentReset();
    }
}

You can view the component in action using your local Dev Server if you have set it up. Use the below links to set up Local Dev Server if you haven’t already done so:

Sample test cases:

  1. Load the c-grandparent-one component in the Local Dev Server – or in your org

2. Try selecting one or two of the Child buttons – verify that the appropriate buttons change the text and color to Deselect (red). Also the corresponding statuses in the parent component should be changed to Selected and the count in the Grandparent component should be incremented accordingly. Feel free to continue clicking to confirming that toggling takes place and the corresponding values reflect in the parent and grandparent components.

3. Clicking on Reset All should reset all the button, statuses and counter back to initial values