DEV Community

Mikhail Petrov
Mikhail Petrov

Posted on • Originally published at Medium on

Event bubbling and handling in React JS and DOM

bubble art

Event bubbling is a concept in the Document Object Model (DOM) of JavaScript. When a user clicks on an element, for example, a , it first runs the click listener on that , then on its parent, then all the way up on other ancestors until it reaches the window. The event pops up as a bubble from the clicked element to the top level.

Let’s add a React.

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

And render 3 nested div’s as React components.

Rendered div blocks

React uses a synthetic event wrapper instead of the native browser event to handle events in the DOM. It subscribes to the root element events only. When clicking div.level3, DOM click listeners are called in the following order: div.level3div.level2div.level1div#root. React is subscribed to div#root and receives click event with target set to div.level3. It generates synthetic event and calls onClick handlers at the same bubbling way div.level3div.level2div.level1. We are still in DOM bubbling phase and the next step is to call listeners on document and window.

Event bubbling scheme

React handlers are called after all div#root children handled DOM events. That’s why calling stopPropagation() inside React handlers only prevents that component’s parents from calling onClick handlers.

function TestComponent() {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current.addEventListener('click', (e) => {
      console.log('Called first');
    });
  }, []);

  return <div onClick={(e) => {
    console.log('Called last');
    e.stopPropagation();
  }} />;
}
Enter fullscreen mode Exit fullscreen mode

React doesn’t receive event if any of its components called DOM stopPropagation().

function TestComponent() {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current.addEventListener('click', (e) => {
      console.log('Called first');
      e.stopPropagation();
    });
  }, []);

  return <div onClick={(e) => {
    console.log('Never called');
  }} />;
}
Enter fullscreen mode Exit fullscreen mode

React bubbles art

Why should one use DOM listeners, if React is a powerful tool for all the tricks with events?

1. Vanilla JS libraries with React wrappers

A lot of libraries created with Vanilla JS are available with React wrappers. React developer should know about event bubbling to interact properly with these libraries.

2. Click-outside task

One of the common tasks is click outside tracking for some popup menu to close it (just subscribe to document click event and check that your component doesn’t contain event.target, example of implementation in ahooks library)

3. Call preventDefault() for passive handlers

Some listeners of React are passive. That’s why implementing custom component behavior for wheel event could be a problem. You can write your logic to handler, but you can’t prevent default scrolling. addEventListener solves this problem.

What was your reason for using addEventListener with React?

And yeah, the picture would not be complete without mentioning the DOM capture phase. But so far I have not had any tasks where I needed to use it with React.

Bubbling scheme with capture phase

The repository with example is available here. It just logs out all phases of click event.

Example console output

Link for the CodeSandbox to play with. Just set stopPropagation prop for any Block component.

CodeSandbox example screenshot

Top comments (0)