Portals Pattern

Overview

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This is especially useful for components like modals, tooltips, and floating menus, which need to visually "break out" of their container's layout constraints while maintaining their position in the React component tree.

Example

Click the button below to open a modal dialogue. The modal will render outside of this container using React's createPortal API, even though it's part of this component's render tree.

Code Implementation

// Modal.tsx
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';

type ModalProps = {
  isOpen: boolean;
  onClose: () => void;
  title: string;
  children: React.ReactNode;
};

const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children }) => {
  // State to track if we're in the browser
  const [mounted, setMounted] = useState(false);
  
  // Mount state on client side only
  useEffect(() => {
    setMounted(true);
    
    // When modal is open, disable body scrolling
    if (isOpen) {
      document.body.style.overflow = 'hidden';
    }
    
    // Cleanup - re-enable scrolling
    return () => {
      document.body.style.overflow = '';
    };
  }, [isOpen]);
  
  // Don't render anything on server or if modal is closed
  if (!mounted || !isOpen) return null;
  
  // Use createPortal to render outside of component hierarchy
  return createPortal(
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
      {/* Overlay */}
      <div className="absolute inset-0 bg-black bg-opacity-75" onClick={onClose} />
      
      {/* Modal */}
      <div className="z-10 bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full">
        {/* Header */}
        <div className="flex justify-between p-4 border-b">
          <h3 className="text-lg font-medium">{title}</h3>
          <button onClick={onClose}>×</button>
        </div>
        
        {/* Body */}
        <div className="p-4">{children}</div>
        
        {/* Footer */}
        <div className="p-4 border-t flex justify-end">
          <button 
            onClick={onClose}
            className="px-4 py-2 bg-blue-500 text-white rounded"
          >
            Close
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
};

export default Modal;

Key Benefits

  • Render content outside the DOM hierarchy of the parent component
  • Avoid CSS issues with z-index, stacking contexts, and overflow
  • Maintain React's event propagation and context through the virtual DOM tree
  • Better accessibility for modal dialogs and popovers
  • Cleaner code organization by keeping related components together
  • Ideal for modals, tooltips, floating menus, and popovers