DEV Community

Cover image for How to Build a React Portal Manager for Dynamic Modals and Tooltips
HexShift
HexShift

Posted on • Edited on

How to Build a React Portal Manager for Dynamic Modals and Tooltips

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)