The CustomEvent API provides a powerful mechanism for implementing custom communication patterns between different parts of your application. Recently, while building a simulated select component, I found this API indispensable and want to share my learnings.
At its core, the CustomEvent architecture consists of three fundamental components that work together to create a robust event system.
The event listener component behaves exactly like standard DOM event listeners. It waits for specific events to be dispatched and executes callback functions when those events occur. This familiar pattern makes CustomEvent intuitive for developers already comfortable with native DOM events.
The event dispatcher component takes responsibility for triggering events under specific conditions. When a particular state change or user interaction occurs, this component creates and dispatches the custom event to the appropriate target.
The event object serves as the communication vessel, carrying payload data from the dispatcher to the listener. Think of it as a courier that delivers information from the event source to the event consumer through the detail property.
Understanding event propagation is crucial when working with CustomEvent. Like native DOM events, custom events respect the three-phase propagation model: capturing, at-target, and bubbling. When a child element dispatches an event with bubbling enabled, both that element and its ancestor chain can receive the event. However, sibling elements of the event source will not trigger their listeners regardless of how they are registered. I initially misunderstood this behavior, so it's worth emphasizing.
Let me demonstrate how CustomEvent works through a practical custom select component example. Consider the following HTML structure:
<div class="dropdown">
<div class="selected-value">No selection</div>
<div class="dropdown-options">
<div>Option One</div>
<div>Option Two</div>
<div>Option Three</div>
<div>Option Four</div>
<div>Option Five</div>
</div>
</div>
The goal is to trigger a custom selection-change event when options are clicked, allowing the root element to listen for and respond to this event. Here's how to implement this:
document.querySelector('.dropdown-options').addEventListener('click', function (event) {
if (event.target.classList.contains('dropdown-options')) {
return;
}
const selectedContent = event.target.textContent;
const selectionEvent = new CustomEvent('selection-change', {
detail: event.target.textContent,
bubbles: true
});
event.target.dispatchEvent(selectionEvent);
});
document.querySelector('.dropdown').addEventListener('selection-change', function (event) {
this.querySelector('.selected-value').textContent = event.detail;
});
When an option is clicked, the handler creates and dispatches a selection-change event carrying the selected option's text content through the detail property. The root dropdown element listens for this event and updates the display accordingly.
At first glance, you might wonder why we don't simply handle this with click events directly. The advantage becomes apparent as applications grow in complexity. Custom events provide a clean separation of concerns, allow multiple listeners to respond to the same action, and create a more maintainable architecture for component communication.