React Portals let you render components outside the main DOM tree — perfect for modals, tooltips, and dropdowns. But managing dozens of portals manually gets messy fast. Here's how to create a clean Portal Manager system in React without external libraries.
Why Use a Portal Manager?
Common use cases:
- Centralized control over all modals, tooltips, and popovers
- Dynamic stacking, layering, and closing logic
- Cleaner separation of UI layers
Step 1: Build a Portal Host
This component will be your centralized container for all portals:
// PortalHost.js import { createContext, useContext, useState } from "react"; import { createPortal } from "react-dom"; const PortalContext = createContext(null); export function PortalHost({ children }) { const [portals, setPortals] = useState([]); const mount = (node) => { const id = Math.random().toString(36).substr(2, 9); setPortals((prev) => [...prev, { id, node }]); return id; }; const unmount = (id) => { setPortals((prev) => prev.filter((p) => p.id !== id)); }; return ( <PortalContext.Provider value={{ mount, unmount }}> {children} {portals.map(({ id, node }) => createPortal(node, document.body) )} </PortalContext.Provider> ); } export function usePortal() { return useContext(PortalContext); }
Step 2: Create a Hook for Dynamic Portals
We’ll build a simple hook to open and close portals:
// useDynamicPortal.js import { usePortal } from "./PortalHost"; import { useEffect, useRef } from "react"; export function useDynamicPortal(content) { const { mount, unmount } = usePortal(); const idRef = useRef(null); useEffect(() => { idRef.current = mount(content); return () => unmount(idRef.current); }, [content, mount, unmount]); }
Step 3: Use It to Create a Modal Dynamically
Example of launching a modal when clicking a button:
// ExampleModal.js import { useState } from "react"; import { useDynamicPortal } from "./useDynamicPortal"; function ModalContent({ onClose }) { return ( <div style={{ position: "fixed", top: "40%", left: "40%", background: "white", padding: "2rem", zIndex: 1000 }}> <p>I'm a modal!</p> <button onClick={onClose}>Close</button> </div> ); } function ExampleModal() { const [open, setOpen] = useState(false); if (open) { useDynamicPortal(<ModalContent onClose={() => setOpen(false)} />); } return <button onClick={() => setOpen(true)}>Open Modal</button>; } export default ExampleModal;
Step 4: Wrap the App with the PortalHost
// App.js import { PortalHost } from "./PortalHost"; import ExampleModal from "./ExampleModal"; function App() { return ( <PortalHost> <ExampleModal /> </PortalHost> ); } export default App;
Pros and Cons
✅ Pros
- Centralized portal management
- No external libraries needed
- Clean separation of modal logic
⚠️ Cons
- More setup compared to a simple createPortal use
- Limited layering control without manual z-indexing
🚀 Alternatives
- React-Aria or Radix UI for more structured UI primitives
- Headless UI modals if you want battle-tested accessibility
Summary
When your React app grows, managing multiple popups, tooltips, and modals becomes chaotic. A Portal Manager brings clean order to the chaos, and this simple implementation is a solid foundation to customize even further for complex use cases.
For a much more extensive guide on getting the most out of React portals, check out my full 24-page PDF file on Gumroad. It's available for just $10:
Using React Portals Like a Pro.
If you found this useful, you can support me here: buymeacoffee.com/hexshift
Top comments (0)