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>
);
And render 3 nested div’s as React components.
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.level3 → div.level2 → div.level1 → div#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.level3 → div.level2 → div.level1. We are still in DOM bubbling phase and the next step is to call listeners on document and window.
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();
}} />;
}
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');
}} />;
}
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.
The repository with example is available here. It just logs out all phases of click event.
Link for the CodeSandbox to play with. Just set stopPropagation prop for any Block component.
Top comments (0)