I was recently working on an implementation of a modal popup in a React.js web app. This project uses portals to conveniently allow any component to inject a popup into a container node very close to the DOM root.
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.https://reactjs.org/docs/portals.html
However, I noticed that pointer events being captured within the popup were somehow interacting with its React ancestors, regardless of where on screen the event happened.
Event bubbling, as it appeared to be, should not be possible – my understanding is that the popup is not being rendered within its React parent’s DOM node but within another DOM node whose only common ancestor with said popup’s parent is the document body.
It’s a wise practice to always read the documentation of a framework feature, and it led me to clarity on this behaviour.
Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.
This includes event bubbling. An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree.https://reactjs.org/docs/portals.html
Ahhhhhhh there it is! It IS event bubbling. To mitigate this, I opted to stop various pointer events within the popup’s containing modal layer by calling
e.stopPropagation(); within respective event handlers attached on the popup’s modal.
You can learn more about React Portals by reading the official documentation found at https://reactjs.org/docs/portals.html