Event Capturing, Bubbling, and Delegation in React

John Rumingan
4 min readApr 10, 2020

--

Every time a user does something on a website — a click of a mouse, a button press — a series of potential events occurs in JavaScript. This is event capturing and bubbling. This allows us to utilize a powerful event handling pattern in JavaScript: event delegation. The idea is that instead of passing an event handler to each individual node, we can place a single event handler on a parent element. This saves us time and allows the parent element to take needed information and manipulate the DOM.

Here’s a basic rundown of the process:

Capture Phase -> Target Phase -> Bubbling Phase
.addEventListener(click, () => {}, true)

Any element that has a true flag in the optional third argument gets called in the capture phase. By default, this is set as false and any of those event listeners are called in the bubbling phase. We can use this phenomenon to use event.target and event.currentTarget.

Let’s say we wanted to make a period table of elements where clicking on a specific element shows us more information about that element:

<table>
<tr>
<th>Periodic table of the elements</th>
</tr>
<tr>
<td class="hydrogen"><strong>H</strong></td>
<td class="helium"><strong>He</strong></td>
</tr>
<tr>
<td class="lithium"><strong>Li</strong></td>
More <td></td> elements
</tr>
<tr>similar lines above</tr>
</table>

We can assign an onClick handler to each <td> element to achieve the results we want. However, doing so would clutter up our beautiful and concise code. Instead, we will assign an onClick to the <table> element. It will use event.target to get the clicked node and add a new class to its classList that will display the details:

let selectedElement;

table.onclick = function(event) {
//finds click, only cares about <td>, and invokes the detail function
let target = event.target;
if (target.tagName != 'TD') return;
detail(target);
};

function detail(newElement) {
//removes the detail class from another node and assigns it to the new chosen element
if (selectedElement) {
selectedElement.classList.remove('detail');
}
selectedElement = newElement;
selectedElement.classList.add('detail');
}

This code is agnostic about the number of <td> elements that exist inside the table. Clicking on this element just adds a “detail” class to the element. We can use CSS and target this class to highlight or emphasize this element so the user knows it’s been selected. However, one little hangup: each <td> element has a <strong> tag. Revised code:

table.onclick = function(event) {
let td = event.target.closest('td');
if (!td) return;
if (!table.contains(td)) return;
detail(td);
};
// or we could use the style below, it's equivalent
table.addEventListener( e => {
let td = e.target.closest('td');
if (!td) return;
if (!table.contains(td)) return;
detail(td);
})

The .closest() method takes advantage of the DOM hierarchy to find the closest parent element with the selector.

We can use delegation further like choosing a specific item from a menu. You can either make a separate click handler for each one or create a handler for the entire menu and use data-set attributes to match a name with a function.

Let’s apply this to React. Click events in React is very similar to handling click events in vanilla JavaScript to manipulate DOM elements. React will use an onClick event listener that takes in a function to be executed once an event happens. Event listeners are passed as props — objects with properties — to each child component so that every instance is initialized with the event listener.

// PeriodicTable.js file 
import React from 'react';
import DetailedElement from './DetailedElement'
import Element from './Element'
import listOfElements from './ListofElementsArrayOfHashes'
//we will assume Element will have the appropriate JSX language including the <td> tags like earlier
//we will also assume DetailedElement does the same but displays the entirety of the information instead of only the element name and abbreviation
class PeriodicTable extends React.Component { //this.state is also acceptable
constructor(){
super();
this.state={
//when an element is clicked, the selectClickedElement() function is invoked and stores the selected element in detailedElement
detailedElement: {},
elementalList: listOfElements,
}}
selectClickedElement(detailedElement){
this.setState({detailedElement})
}
//for simplicity's sake, I will render all <Element /> components to a single <tr>, even though we could add further logic to display the table of elements correctly. render(){
<div>
{this.state.detailedElement?
<DetailedElement details={this.state.detailedElement}/> : null
}
<table><tr><th>Periodic Table of the Elements</th></tr>
<tr>
{this.state.elementalList.map(element => {
<Element clickHandler={this.selectClickedElement}
information={element}/>
})}
</tr>
</table>
</div>
}
}

What we have here is a React component that renders a chosen detailed component on top of a group of Element components. Essentially, we’re using the same idea of event delegation being handled in the parent component. This component maps the list of elements into individual components and assigns them the correct props — detailed information about that object and a click handler to render the correct information with the DetailedElement component. Easy!

I delegate you!

I hope this helps anyone who’s currently trying to understand these concepts while transitioning from JavaScript to React. Event delegation is a simple but powerful concept that allows us to create more dynamic apps with less effort.

--

--