4

I'm encountering some behavior in LWC that runs counter to my understanding of Event.target -- namely, that it is supposed to represent the DOM element that originally dispatched the event.

Here is an example where a grandchild component dispatches the event and the parent component receives it. event.target is reflecting the child component, not the grandchild.

parent.html

<template>
    <div onmyevent={handleMyEvent}>
        <c-child></c-child>
    </div>
</template>

parent.js

import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {
  handleMyEvent(event) {
    console.log(event.target.tagName); // Receiving "C-CHILD" (expecting "C-GRANDCHILD")
  }
}

child.html

<template>
  <c-grandchild></c-grandchild>
</template>

grandchild.html

<template>
  <button onclick={handleButtonClick}>Dispatch event</button>
</template>

grandchild.js

import { LightningElement } from "lwc";

export default class Grandchild extends LightningElement {
  handleButtonClick() {
    this.dispatchEvent(new CustomEvent("myevent", { bubbles: true, composed: true }));
  }
}

Here is a demo: https://stackblitz.com/edit/salesforce-lwc-ezdzna?file=src%2Fmodules%2Fx%2Fapp%2Fapp.html

I would like the parent component to perform an action on the grandchild component when the event is received. How can this be accomplished?

1 Answer 1

5

This is the expected behavior, as specified in the documentation.

When an event bubbles up the DOM, if it crosses the shadow boundary, the value of Event.target changes to match the scope of the listener. This change is called “event retargeting.” The event is retargeted so the listener can’t see into the shadow DOM of the component that dispatched the event. Event retargeting preserves shadow DOM encapsulation.

In other words, if you wanted to see c-grandchild as the target, you need to have the c-grandchild within the parent's Shadow DOM, as demonstrated below, using a fork of your own code.

<div onmyevent={handleMyEvent}>
    <x-child>
        <x-grandchild></x-grandchild>
    </x-child>
</div>
<template>
    <div style="background-color: lightyellow; padding: 1rem;">
        <slot></slot>
    </div>
</template>

Demo.

2
  • Thanks -- good answer as always. Unfortunately, I can't solve my problem this way, since in my actual use case since I am building components for LWR sites where components are dragged and dropped into slots in Experience Builder, not hard-coded. I think I will end up needing to add event handler code to my intermediary components that sit in between the components that fire these events and the top-level component that registers them. Unless you have any more tricks up your sleeve! Mar 24 at 17:53
  • 2
    @MatthewSouther I've got a lot of them. You can import a shared variable to gain access across components. You can use Lightning Message Service to communicate across components anywhere in the DOM. The older pubsub model also works pretty well for light communications. You could create a named-pipe library. Etc, etc.
    – sfdcfox
    Mar 24 at 18:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .