Event Bubbling
Event Bubbling Through Portals
This is one of the most important and often misunderstood aspects of Portals.
Rule: Events fired inside a Portal bubble up through the React component tree,
NOT through the DOM tree. This is by design.
Example
import { createPortal } from 'react-dom';
function Parent() {
function handleClick() {
console.log('Parent caught the click!');
// This fires even though the button is inside a Portal
// mounted outside Parent's DOM node
}
return (
<div onClick={handleClick}>
<p>I am the parent.</p>
{createPortal(
<button>Click me (I'm in a Portal)</button>,
document.getElementById('portal-root')
)}
</div>
);
}
Clicking the button triggers handleClick in Parent — even though the button’s DOM
node is inside #portal-root, far from div onClick in the physical DOM.
React Tree vs DOM Tree Bubbling Comparison
| Aspect | React Component Tree | Real DOM Tree |
|---|---|---|
| Events bubble through | Yes (Portal’s logical parent) | No (Portal’s DOM parent is different) |
| Context available | Yes (inherits from logical parent) | N/A |
| React state inheritance | Yes | N/A |
| CSS inheritance | No (follows physical DOM parent) | Yes (follows physical DOM parent) |
Stopping Portal Event Bubbling
// Prevent events from Portal reaching the React parent
{createPortal(
<div onClick={(e) => e.stopPropagation()}>
{/* Content inside won't bubble to Portal's React parent */}
<button>Click</button>
</div>,
document.body
)}
