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.
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.
// 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;