Event Bubbling Through Portals Explained0%
Event Bubbling Through Portals Explained
Beginner12 min read•Updated: Jul 11, 2026
Event Bubbling Through Portals Explained
1. The Surprising Portal Event Behavior
One of the most fascinating aspects of React Portals is how events behave:
Even though a portal is mounted into a completely different DOM node in the physical HTML, events fired inside the portal bubble UP through the REACT COMPONENT TREE, NOT through the physical HTML DOM tree!
Consider this scenario:
Output
React Component Tree:
<ParentDiv onClick={handleParentClick}>
<ChildWithPortal>
└─ createPortal(<button>Click</button>, document.body)
In the physical HTML DOM:
- The
<button>is a direct child of<body>. - The
<ParentDiv>is located deep inside<div id="root">. - Physically,
<button>is NOT inside<ParentDiv>.
Yet, if you click the <button>: handleParentClick on <ParentDiv> WILL FIRE!
Output
Physical HTML DOM:
<body>
<div id="root"> ──> <ParentDiv> (Not an ancestor of button!) </div>
<button id="portal-btn">Click Me</button>
</body>
React Virtual DOM Event Flow:
[button clicked] ════(Bubbles through React Tree!)════► [ParentDiv handleParentClick fires!]
2. Why Does This Happen?
React does not rely on native DOM event listeners attached to individual elements.
As explored in our SyntheticEvent architecture lessons:
- React attaches unified listeners at the root of the React tree (
<div id="root">). - React's synthetic event dispatcher traverses the Virtual DOM component hierarchy, not the browser's physical DOM tree.
- Because
<ChildWithPortal>is a logical child of<ParentDiv>in the React tree, React bubbles theSyntheticEventup through the component hierarchy as expected!
3. Practical Demonstration
JSX
import React, { useState } from 'react';
import { createPortal } from 'react-dom';
function PortalModal({ onClose }) {
return createPortal(
<div className="portal-dialog">
<p>Inside Portal (Physical child of document.body)</p>
<button className="btn-action">Action Button</button>
</div>,
document.body
);
}
export default function Container() {
const [clickCount, setClickCount] = useState(0);
// This handler will catch clicks from inside the portal!
const handleContainerClick = () => {
setClickCount(prev => prev + 1);
};
return (
<div className="parent-box" onClick={handleContainerClick}>
<h3>Parent Container (Clicks captured: {clickCount})</h3>
<p>Clicking the button inside the portal WILL increment this count!</p>
<PortalModal />
</div>
);
}
4. How to Prevent Unintended Bubbling
If your portal represents a modal or tooltip and you do not want its internal click events to trigger parent container handlers:
- Call
e.stopPropagation()inside your portal event handler!
JSX
// Stop event from bubbling up the React component tree:
<div onClick={(e) => e.stopPropagation()}>
{/* Children inside this container will NOT trigger parent handlers */}
</div>
Interactive Quiz
Practice Quiz
Question 1
How do SyntheticEvents bubble when an element is rendered through
createPortal?Question 2
If
<Parent onClick={handleClick}> renders a portal that attaches a <button> to document.body, what happens when that button is clicked?Question 3
Why does React implement event bubbling based on the React tree rather than the DOM tree?
Question 4
How can an engineer stop a portal's click events from reaching ancestor React components?
Question 5
If a native browser DOM event listener is attached via
document.addEventListener('click'), will it receive portal clicks?Next Lesson
Component Composition as an Alternative to Context
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| Building Modal Dialogs and Overlays with Portals | Component Composition as an Alternative to Context |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.