React Portals: DOM Detachment vs Virtual Tree Event Bubbling
- Last updated
- Prerequisites:
- SyntheticEvent system
- DOM Tree hierarchy vs React Component Tree
- react
- createPortal
- dom
- event-bubbling
- modals
- synthetic-events
Read at your depth
The practical view
`createPortal(children, container)` renders a component's physical DOM nodes into a different part of the document (such as `document.body` or a modal container) while keeping its position in the React component tree. Even though the portal's DOM element lives in a completely different place in the HTML, **React synthetic events still bubble up through the React component tree**, not the physical DOM tree.
The same idea in other frameworks
`createPortal(<Modal />, document.body)` with React Fiber event bubbling.Angular CDK `Overlay` / `PortalHost` rendering into overlay containers.`<Teleport to="body"><Modal /></Teleport>` (events follow native DOM bubbling).Svelte custom action moving node to body or third-party portal library.Legacy vs modern
DOM-Trapped Modal vs createPortal with Virtual Bubbling
Standard nested modals get clipped by parent `overflow: hidden`; Portals mount to `document.body` while maintaining parent event capture.
<div className="modal">...</div> // ❌ Clipped by parent CSS overflow: hiddencreatePortal(<ModalContent />, document.body) // ✅ NEW: Escapes CSS constraints, preserves React eventsInterview gotchas
Context
This tests whether you understand the fundamental difference between React SyntheticEvent bubbling and native DOM bubbling.
The mechanical answer
Yes, the React parent's `onClick` will fire. React synthetic event bubbling follows the **React Component (Fiber) Tree**, not the physical HTML DOM tree. When a click occurs inside a portal mounted to `document.body`, the native event bubbles to the root container, and React dispatches the event upward through the component hierarchy that declared the portal.
Trap
A native `element.addEventListener('click')` on the parent DOM node will *not* catch the click, but a React `onClick` prop *will* catch it.
Context
Checks knowledge of component hierarchy boundaries and data propagation.
The mechanical answer
Portals retain complete access to all React Context providers and Error Boundaries defined by their virtual parents. If a component inside `createPortal` calls `useContext(MyContext)` or throws an error, React resolves the Context and catches the error from the parent hierarchy where the portal was rendered in JSX, completely ignoring the portal's physical DOM destination.
Trap
Do not re-wrap portal children in duplicate Context Providers; they already inherit context from their virtual call site.