React - Portals
Overview
Estimated time: 15–25 minutes
Portals let you render UI into a DOM node that exists outside your component’s own DOM hierarchy (useful for modals, tooltips).
Learning Objectives
- Understand how to render into an external container with createPortal.
- Build a basic modal using portals.
Try it: Modal via Portal
View source
const { useState } = React;
function Modal({ children, onClose }){
const el = document.getElementById('portal-root');
return ReactDOM.createPortal(
<div style={{position:'fixed', inset:0, background:'rgba(0,0,0,.5)'}} onClick={onClose}>
<div style={{margin:'10% auto', padding:20, background:'#fff', color:'#111', width:300, borderRadius:8}} onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
el
);
}
function App(){
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open Modal</button>
{open && <Modal onClose={() => setOpen(false)}><p>Hello from a Portal!</p></Modal>}
</div>
);
}
ReactDOM.createRoot(document.getElementById('try-portal')).render(<App />);
Syntax primer
ReactDOM.createPortal(children, container)
renders children into a DOM node elsewhere.
Common pitfalls
- Click events bubble through portals as if children were inside the parent—handle propagation carefully.
Checks for Understanding
- Do events inside a portal bubble to ancestors outside the portal container?
- Why might you choose a top-level
#portal-root
element?
Show answers
- Yes. Event bubbling follows the React tree, not the DOM containment of the portal.
- To centralize overlays/modals above the rest of the app and avoid clipping/stacking issues.
Exercises
- Add focus trapping inside the modal and restore focus to the trigger on close.
- Make the modal dismissible via Escape key and clicking the backdrop.
- Support nested modals; ensure backdrop click only closes the topmost modal.