How Do You Implement a React Portal in a React Application?
I once spent two hours debugging a modal that was invisible. Turns out a parent had overflow: hidden and the modal was clipped. The React tree said the mo...
24 Apr 2024

I once spent two hours debugging a modal that was invisible. Turns out a parent had overflow: hidden and the modal was clipped. The React tree said the modal was a child of that parent. The DOM agreed. And the CSS did what CSS does.
React portals solve this. They let you render a component into a different DOM node while keeping it in the same React tree. The component stays connected to its parent for events and context, but it physically lives somewhere else in the DOM.
This is the escape hatch for modals, tooltips, and anything that needs to break out of its container's CSS constraints.
Building a portal
Two steps. First, create a DOM node for the portal to render into. Second, use ReactDOM.createPortal() to render your component there.
import React, { useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'
interface PortalProps {
children: React.ReactNode
}
const Portal: React.FC<PortalProps> = ({ children }) => {
const elRef = useRef<HTMLDivElement | null>(null)
if (!elRef.current) {
elRef.current = document.createElement('div')
}
useEffect(() => {
const portalRoot = document.getElementById('portal-root')
if (!portalRoot || !elRef.current) return
portalRoot.appendChild(elRef.current)
return () => {
portalRoot.removeChild(elRef.current!)
}
}, [])
return ReactDOM.createPortal(children, elRef.current)
}
export default Portal
Add a <div id="portal-root"></div> to your index.html, outside your app root. Now any component wrapped in <Portal> renders into that node.
Why this matters
React's event system still works through portals. A click event inside a portal bubbles up through the React component tree, not the DOM tree. This means a parent component can catch events from a portaled child, even though they're in different DOM nodes.
Context works the same way. A portaled component can still access useContext values from its React ancestors.
The trade-offs
Portals add complexity. You now have components that live in two trees -- the React tree and the DOM tree -- and they don't match. This can confuse CSS debugging and testing tools that rely on DOM structure.
You also need to manage the portal mount point. If portal-root doesn't exist yet when your portal mounts, it breaks silently.
For simple tooltips or dropdowns, sometimes repositioning with CSS (position: fixed) is simpler than reaching for a portal. Use portals when CSS alone can't solve the containment problem.