React Patterns

Advanced Portal Patterns

Nested portals, event delegation, z-index management, and focus trapping.

Advertisement

Advanced Portal Patterns

Nested portals, event delegation, z-index management, and focus trapping.

Overview

Portals enable rendering outside the DOM hierarchy while maintaining React context.

Key Concepts

  • Portal Containers — Multiple portal roots for different z-levels
  • Event Delegation — Handle events across portal boundaries
  • Z-Index Management — Prevent portal stacking issues
  • Focus Trapping — Keep focus within modal portals
  • SSR Compatibility — Handle server-side rendering gracefully

Code Examples

// Portal container management
function PortalProvider({ children }) {
  const [portals, setPortals] = useState([]);
  const [nextId, setNextId] = useState(0);

  const createPortal = useCallback((content, options = {}) => {
    const id = nextId;
    setNextId(n => n + 1);
    setPortals(prev => [...prev, { id, content, ...options }]);
    return () => setPortals(prev => prev.filter(p => p.id !== id));
  }, [nextId]);

  return (
    <PortalContext.Provider value={createPortal}>
      {children}
      {portals.map(portal => createPortal(portal.content, portal.containerId))}
    </PortalContext.Provider>
  );
}

// Focus trap for modals
function useFocusTrap(ref) {
  useEffect(() => {
    if (!ref.current) return;
    
    const focusableElements = ref.current.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    const handleKeyDown = (e) => {
      if (e.key !== 'Tab') return;
      
      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          lastElement.focus();
          e.preventDefault();
        }
      } else {
        if (document.activeElement === lastElement) {
          firstElement.focus();
          e.preventDefault();
        }
      }
    };

    ref.current.addEventListener('keydown', handleKeyDown);
    firstElement?.focus();
    
    return () => ref.current?.removeEventListener('keydown', handleKeyDown);
  }, [ref]);
}

Practice

Build a toast notification system with portal management and focus handling.