React Portals for Modal Popups
In React, components render inside their parent DOM element tree. However, for UI elements like modal dialogs, tooltips, or overlays, this structure can cause issues with CSS properties like overflow: hidden or z-index stacking contexts. React Portals solve this by rendering elements into a different DOM node altogether.
1. What is a Portal?
Portals allow you to render a child component into a DOM node that exists outside the DOM hierarchy of the parent component, while still preserving its React component state and event propagation behavior.
2. Using ReactDOM.createPortal
To create a portal, import createPortal from react-dom. The function accepts two arguments:
- child: Any renderable React node (such as JSX).
- container: The target DOM element where you want to mount the node (typically an element at the document root level).
import { createPortal } from "react-dom";
createPortal(child, container);3. Implementing a Modal Component
Let us build a modal dialog component that renders into an element with the ID modal-root located at the document body level:
import { useEffect } from "react";
import { createPortal } from "react-dom";
export function Modal({ isOpen, onClose, children }) {
// Create a container div dynamically or target an existing one
const modalRoot = document.getElementById("modal-root") || document.body;
if (!isOpen) return null;
return createPortal(
<div className="modal-backdrop" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>×</button>
{children}
</div>
</div>,
modalRoot
);
}Make sure your index.html has a corresponding mount point:
<body>
<div id="root"></div>
<!-- Portals mount here -->
<div id="modal-root"></div>
</body>4. Event Bubbling Through Portals
Even though a portal renders in a different location in the physical DOM tree, it still behaves like a normal React child component in all other ways.
This means that event bubbling continues to work normally. An event (like a click) triggered inside a portal will bubble up to its ancestors in the React component tree, even if they are not ancestors in the HTML DOM tree.