Event bubbling and capturing in React
Tags: react, webdev
Published: 2023-02-22 19:36:16

**Written by [Isaac Okoro](https://blog.logrocket.com/author/isaacjunior/)✏️**

Events are detectable actions or occurrences commonly used in programs to trigger functions. These actions include a user clicking a button and a webpage loading. 

This article will cover how event capturing and bubbling work in [React](https://blog.logrocket.com/tag/react/). Before we go over what event capturing and bubbling are, we’ll learn about event delegation and how it works. We will also look at the [`SyntheticEvent`](https://reactjs.org/docs/events.html) in React, how it works, and its connection to event capturing and bubbling in React. 

_Jump ahead:_

*   [Understanding event delegation in React](#understanding-event-delegation-react)
*   [Intro to event propagation](#intro-event-propagation)
*   [What is the `SyntheticEvent`?](#synthetic-event)
*   [Using `event.stopPropagation()` in React](#using-event-stop-propagation-react)
*   [Event delegation in React v16 vs. React v17](#event-delegation-react-v16-v17)
    *   [Changes to the DOM tree](#changes-dom-tree)

## Understanding event delegation in React <a name="understanding-event-delegation-react">

Event delegation is a technique for handling events that involves adding a single event listener to a parent element rather than adding [event listeners](https://blog.logrocket.com/deep-dive-into-event-bubbling-and-capturing/) to individual child elements. When an event occurs on a child element, the event listener on the parent element is notified, and the parent element can handle the event. This is useful when you have many child elements and want to avoid adding event listeners to them. 

Imagine a scenario in which you have the following code:

```javascript
function App() {
  const parentFunction = () => {
    console.log('Hello from the parent');
  };
  const firstChildFunction = () => {
    console.log('Hello from the first child');
  };
  const secondChildFunction = () => {
    console.log('Hello from the second child');
  };
  const thirdChildFunction = () => {
    console.log('Hello from the third child');
  };
  return ( 
    <div className="App">
      <div className="parent-div" onClick={parentFunction}>
        <button className="first-child-button" onClick={firstChildFunction}>
          First child button
        </button>
        <button className="second-child-button" onClick={secondChildFunction}>
          Second child button
        </button>
        <button className="third-child-button" onClick={thirdChildFunction}>
          Third child button
        </button>
      </div>
    </div>
  );
}
export default App;
```

In the code above, we have a parent `div` with an [`onClick`](https://blog.logrocket.com/react-onclick-event-handlers-guide/) handler attached to it. The parent `div` houses three children `buttons` with `onClick` handlers attached to them. The `onClick` handles all the `PointTo` functions that print text to the console. 

When we click any of the buttons and check the console, we get the log for the button, which is the second button. However, we also get another log that shows that the parent `div` was clicked, as shown in the image below: ![React Child Component](https://blog.logrocket.com/wp-content/uploads/2023/02/react-child-component.png) 

We know that event delegation took place because the parent element also handles the event of the children element. With that out of the way, let’s look at the order in which React handles the event.

## Intro to event propagation <a name="intro-event-propagation">

Event propagation is the order in which event handlers are called when an event occurs on a webpage. To understand how event propagation works, it helps to know about the three phases of [event flow in the DOM](https://blog.logrocket.com/react-reference-guide-refs-dom/): the capturing phase, the target phase, and the bubbling phase. 

In the capturing phase, the event propagates from the top of the document down to the element on which the event occurred. Capturing is less commonly used than bubbling. Simply put, this is otherwise known as an event moving downwards from the top of the document to the element on which the said event occurred. 

In the target phase, the event handler is executed on the element on which the event occurred and can be assessed via `event.target`. 

In the bubbling phase, the event moves back up the DOM tree from the element where the event occurred to its parent element, and so on, until it reaches the top of the document. This is the default behavior for most events in the DOM. This is the reverse of the capturing phase. 

The image below describes the three phases of event propagation and how it works: 

![React Event Propagation Diagram](https://blog.logrocket.com/wp-content/uploads/2023/02/event-capturing-react.png)

> Note: There is a fourth phase of event propagation known as the none phase, where no event is executed.

## What is the `SyntheticEvent`? <a name="synthetic-event">

React relies on a [virtual DOM](https://blog.logrocket.com/virtual-dom-react/) and uses this concept to handle DOM operations. The [`SyntheticEvent`](https://blog.logrocket.com/getting-started-react-synthetic-event/) wrapper wraps around the native DOM event that React uses to abstract the differences between the events the browser can handle. This allows you to [write event handlers that work consistently across all browsers](https://blog.logrocket.com/creating-hover-events-synthetic-event-react-hover/). 

For example, consider the following code that attaches a click `EventListener` to a `button`:

```javascript
document.getElementById('myButton').addEventListener('click', () => {
  console.log('Button was clicked');
});
```

In the code above, the `click` event is a native DOM event. In React, you would write that event like so:

```javascript
<button onClick={() => console.log('Button was clicked')}>Click me</button>
```

The `onClick` event in this example is a synthetic event. React will call the event handler function you provided when the button is clicked. Internally, React will convert the synthetic event into a native DOM event and pass it to the event listener. 

React creates `SyntheticEvents` to allow you to write your event handlers in a way familiar to you without worrying about the differences between the various events the browser can handle. `SyntheticEvents` also include methods such as `stopPropagation` and `preventDefault`, which are instrumental to React's Event System.

## Using `event.stopPropagation()` in React <a name="using-event-stop-propagation-react">

We have looked at event propagation, its phases, and how they work. However, there are occasions when you don't want an event to bubble. This is where the `event.stopPropagation()` method comes in. 

In React, the `event.stopPropagation()` method prevents an event from being handled by the parent component if a child component handles it. You can call `event.stopPropagation()` on the event object to stop the event from propagating further up the tree. This is useful in cases where the parent component has an event handler that conflicts with the event handler of the child component. 

Let's see an example of a use case where you would want to use `event.stopPropagation()` to avoid conflict between the parent and child components. In the code below, we have a `header` component that has a `button` for logging out:

```javascript
import './App.css';
function App() {
  const handleHeaderClick = () => {
    console.log('Header was clicked');
  };
  const handleButtonClick = () => {
    console.log('The logout button was clicked');
  };
  return (
    <>
      <div className="styleHeader" onClick={handleHeaderClick}>
        <div>Header</div>
        <button type="button" onClick={handleButtonClick}>
          Log Out
        </button>
      </div>
    </>
  );
}
export default App;
```

The code block above will display the `header` component as shown in the image below: ![Example of React Header Logged](https://blog.logrocket.com/wp-content/uploads/2023/02/react-header-log.png) 

In such cases, we don't want the event handled by the parent component. At this point, if you click the **logout button**, the parent still handles the event as shown below: ![React Event Clicked](https://blog.logrocket.com/wp-content/uploads/2023/02/log-out-react.png) 

We can stop the parent component from handling the event by passing the `event.stopPropagation()` to the child component — the button — as shown below:

```javascript
import './App.css';
function App() {
  const handleHeaderClick = () => {
    console.log('Header was clicked');
  };
  const handleButtonClick = (event) => {
    event.stopPropagation();
    console.log('The logout button was clicked');
  };

// The code down here remains the same 
}
export default App;
```

We first passed the `event` object as a parameter to the `handleButtonClick` function and then used `event.stopPropagation()` to stop the event from bubbling into the parent component. Now, if we click the **button**, no bubbling occurs, and we only get the log for the button component. It should look like this: 

![React Event Capture Example](https://blog.logrocket.com/wp-content/uploads/2023/02/log-out-two-react.png)

## Event delegation in React v16 vs. React v17 <a name="event-delegation-react-v16-v17">

In React v16 and earlier, event delegation was handled by attaching event handlers at the Document node per event. This meant React would figure out which component was called during an event and bubble that event to the Document node where React had attached event handlers. 

There were [issues](https://github.com/facebook/react/pull/8117) with this method because of the nested [React trees](https://blog.logrocket.com/comparing-react-tree-components/). If you have nested React trees in your application, the `event.stopPropagation()` method will not work as intended. Let's look at an example below:

```html
// Nested React trees in the index.html
<div id="parent">
   <div id="root"></div>
</div>
```

Paste the following code into the `App.js` file:

```javascript
import "./styles.css";
import React from "react";

export default function App() {
  const handleButtonClick = () => {
    console.log("The button was clicked");
  };
  return (
    <div className="App">
      <button onClick={handleButtonClick}>Click me</button>
    </div>
  );
}
```

In the code block above, we have a `button` with an `onClick` handler that points to a function that logs some text to the console when clicked. 

In the `index.js` file, paste the code below into it:

```javascript
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

const parent = document.getElementById("parent");
parent.addEventListener("click", (e) => {
  console.log("The parent was clicked");
  e.stopPropagation();
});

ReactDOM.render(<App />, document.getElementById("root"));
```

In the code block above, we got the `parent` element from the DOM and attached an `EventListener` to it. The `EventListener` listened for a `click` event and logged a different text to the console. Then, we called the `event.stopPropagation()` method in the following line. 

We expect to get two different text strings when we click the button. But, instead, we get one, and it's not even the one that we are expecting, as shown in the image below: ![Example of React Bubbling Tree](https://blog.logrocket.com/wp-content/uploads/2023/02/react-bubbling-tree.png) 

This happens because when you have nested React trees, React will still bubble events to the outer tree even after `event.stopPropagation()` has been called.

### Changes to the DOM tree <a name="changes-dom-tree">

In React v17 and later, the developers of React changed how it attaches events to the DOM tree. The event handlers are now attached to the root DOM container that renders your React tree and is no longer attached to the Document node. Here’s a visualization of those changes: ![A Diagram Showing How React v17 Attaches Events to the Roots Rather Than to the Document](https://blog.logrocket.com/wp-content/uploads/2023/02/react-tree-changes.png) 

If we change the `index.js` file to reflect React v17 changes, we get the appropriate logs in the console, as shown below:

```javascript
// index.js
import { createRoot } from "react-dom/client";
import App from "./App";

const parent = document.getElementById("parent");
parent.addEventListener("click", (e) => {
  console.log("The parent was clicked");
  e.stopPropagation();
});

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App />);
```

Now, when we click the **button**, we get the logs, as shown in the image below: ![React Event Capturing and Bubbling](https://blog.logrocket.com/wp-content/uploads/2023/02/react-event-capture-bubble-final.png)

## Conclusion

This article looked at how events are handled in React. We looked at event delegation, propagation, bubbling, capturing, and `SyntheticEvents` in React. In addition, we looked at the `event.stopPropagation()` method, and how we can use it to stop event propagation. We also looked at how events were handled in previous versions of React and how events are handled in the current versions of React.

---
##[LogRocket](https://lp.logrocket.com/blg/react-signup-general): Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket](https://lp.logrocket.com/blg/react-signup-general).

[![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/react-signup-general)

[LogRocket](https://lp.logrocket.com/blg/react-signup-general) combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free](https://lp.logrocket.com/blg/react-signup-general).