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-rootelement?
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.