React Portals: the three use cases that actually matter
← Back
April 4, 2026React5 min read

React Portals: the three use cases that actually matter

Published April 4, 20265 min read

I understood what React Portals were for two years before I actually needed one. Then I built a tooltip inside an overflow:hidden container, watched it get clipped, and remembered portals existed. They solve a specific class of problems — here are the three cases where they are genuinely the right tool.

What createPortal does

typescript
import { createPortal } from 'react-dom';

function MyPortal({ children }: { children: React.ReactNode }) {
  // Renders children into document.body, not into MyPortal's parent DOM node
  // But children are still in MyPortal's React tree (events bubble, context works)
  return createPortal(children, document.body);
}

Use case 1: Tooltips inside overflow:hidden containers

This is the most common portal use case. A tooltip inside a overflow: hidden table cell gets clipped. Portaling it to document.body escapes the clip:

typescript
import { useState, useRef, createPortal } from 'react';

function Tooltip({
  children,
  content,
}: {
  children: React.ReactNode;
  content: string;
}) {
  const [visible, setVisible] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const triggerRef = useRef(null);

  function showTooltip() {
    if (!triggerRef.current) return;
    const rect = triggerRef.current.getBoundingClientRect();
    setPosition({
      top: rect.bottom + window.scrollY + 8,
      left: rect.left + window.scrollX + rect.width / 2,
    });
    setVisible(true);
  }

  return (
    <>
       setVisible(false)}
      >
        {children}
      

      {visible && createPortal(
        
{content}
, document.body )} ); }

Use case 2: Modals that need to overlay everything

typescript
import { createPortal, useEffect } from 'react';

function Modal({
  isOpen,
  onClose,
  children,
}: {
  isOpen: boolean;
  onClose: () => void;
  children: React.ReactNode;
}) {
  // Prevent body scroll while modal is open
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
      return () => { document.body.style.overflow = ''; };
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return createPortal(
    
{ if (e.target === e.currentTarget) onClose(); }} >
{children}
, document.body // Renders at top level, overlays everything ); }

Use case 3: Notification toasts from anywhere in the tree

typescript
// context/toast.tsx
import { createContext, useContext, useState, createPortal } from 'react';

interface Toast {
  id: string;
  message: string;
  type: 'success' | 'error' | 'info';
}

const ToastContext = createContext<{
  addToast: (message: string, type?: Toast['type']) => void;
} | null>(null);

export function ToastProvider({ children }: { children: React.ReactNode }) {
  const [toasts, setToasts] = useState([]);

  function addToast(message: string, type: Toast['type'] = 'info') {
    const id = crypto.randomUUID();
    setToasts((prev) => [...prev, { id, message, type }]);
    setTimeout(() => {
      setToasts((prev) => prev.filter((t) => t.id !== id));
    }, 4000);
  }

  return (
    
      {children}
      {/* Toast container portaled to document.body */}
      {createPortal(
        
{toasts.map((toast) => (
{toast.message}
))}
, document.body )}
); } export const useToast = () => { const ctx = useContext(ToastContext); if (!ctx) throw new Error('useToast must be used within ToastProvider'); return ctx; }; // Usage anywhere in the tree: function SomeComponent() { const { addToast } = useToast(); return ( ); }

The key property: React tree vs DOM tree

Events from portal children still bubble through the React component tree, not the DOM tree. This means a click inside a portaled modal will bubble to the modal's parent component in React — which is usually what you want for event handling and context access.

If you find yourself reaching for portals for other use cases, check if a simpler solution exists first. Portals add complexity. But for these three cases — tooltips in clipping containers, modals, and global notifications — they are the cleanest solution available.

Share this
← All Posts5 min read