Read the screenplay: FANNIEGATE — $7 trillion. 17 years. The biggest fraud in American capital markets.
#39⚛️ LWCAnnoying

Custom Events Not Reaching Parent Components

CustomEvent doesn't bubble or compose by default. Your parent never heard you.

What Happened

Had a grandchild component dispatching a 'save' event. The parent two levels up had an onsave handler. Nothing happened. I added console.logs in every component. The event was firing but dying at the first shadow DOM boundary. In React, events bubble by default. In LWC, they don't. I burned two hours on what was a one-line fix.

The Wrong Way

// grandchildComponent.js - event dies at shadow boundary
handleClick() {
  this.dispatchEvent(new CustomEvent('save', {
    detail: { recordId: this.recordId }
    // bubbles defaults to false
    // composed defaults to false
  }));
}

<!-- parentComponent.html - never receives the event -->
<template>
  <c-child-wrapper onsave={handleSave}>
    <!-- grandchild is inside child-wrapper's template, event can't escape -->
  </c-child-wrapper>
</template>

The Right Way

// Option A: Bubble + compose through shadow DOM boundaries
// grandchildComponent.js
handleClick() {
  this.dispatchEvent(new CustomEvent('save', {
    detail: { recordId: this.recordId },
    bubbles: true,
    composed: true // crosses shadow DOM boundaries
  }));
}

// Option B (preferred): Re-dispatch at each level
// childWrapper.js - catch and re-dispatch
handleGrandchildSave(event) {
  this.dispatchEvent(new CustomEvent('save', {
    detail: event.detail
  }));
}

<!-- childWrapper.html -->
<template>
  <c-grandchild onsave={handleGrandchildSave}></c-grandchild>
</template>

The Lesson

LWC events don't bubble or compose by default. For cross-boundary events, either set bubbles + composed, or re-dispatch at each level for better encapsulation.

Don't make this mistake.

Hire someone who already did.

View Consulting →

Enjoyed this? Get more like it.

Glen's Musings — AI, investing, and building things. Occasional. Free.

More LWC Mistakes