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.