React usePortal Hook
react-useportal is a utility React hook for creating and managing React Portals, a feature introduced in React 16 that allows components to render children into a DOM node that exists outside the DOM hierarchy of the parent component. Currently at version 1.0.19, the package simplifies common use cases such as modals, tooltips, dropdowns, and notifications. Key differentiators include built-in isomorphic Server-Side Rendering (SSR) support, full TypeScript compatibility, and a minimal dependency footprint (primarily `use-ssr`). Despite its utility, the project has seen very limited maintenance and updates in recent years, making its release cadence effectively dormant. It aims to abstract away the direct usage of `ReactDOM.createPortal` for a more React-hook-centric API.
Common errors
-
ReferenceError: document is not defined
cause Attempting to access the `document` object during server-side rendering (SSR) without conditional checks, typically when using `bindTo: document.getElementById('...')`.fixGuard `document` access with `typeof document !== 'undefined' ? document.getElementById('my-id') : null` to ensure code runs only in a browser environment. -
Error: Target container is not a DOM element.
cause The DOM element specified in the `bindTo` option either does not exist, or the component attempting to create the portal mounts before the target element is available in the DOM.fixEnsure the target DOM element exists before the portal tries to mount. For dynamically rendered targets, use a `ref` or `useEffect` to ensure the element is ready. For static targets, verify the ID matches an element in your `index.html`. -
Events on elements inside the Portal bubble to parent components in the React tree, not the DOM tree.
cause React's event system processes events through the React component tree, regardless of the physical DOM placement of portals. This can be counter-intuitive for elements visually outside their parent.fixUse `event.stopPropagation()` on event handlers within the portal's content or on its overlay to prevent events from bubbling up to undesired parent components in the React tree.
Warnings
- gotcha The `react-useportal` package has not seen active development or maintenance in over three years. While functional for many use cases, it may not receive updates for compatibility with future React versions or address new bugs/security concerns.
- gotcha When using the `bindTo` option with `document.getElementById` or similar DOM APIs, ensure proper checks for `document` availability, especially in Server-Side Rendering (SSR) environments. Direct access to `document` on the server will cause errors.
- gotcha When creating many `usePortal` instances, especially for elements like tooltips that might appear on hover, there can be performance overhead due to `ReactDOM.createPortal()` being called immediately for each instance. This can lead to noticeable lag if not managed.
- gotcha Portals, including those created with `react-useportal`, do not automatically handle accessibility concerns such as focus trapping, keyboard navigation, or ARIA attributes for modal dialogs. Implementing these correctly is crucial for an inclusive user experience.
Install
-
npm install react-useportal -
yarn add react-useportal -
pnpm add react-useportal
Imports
- usePortal
import { usePortal } from 'react-useportal'import usePortal from 'react-useportal'
- Portal
import { Portal } from 'react-useportal'const { Portal } = usePortal() - UsePortalOptions
import type { UsePortalOptions } from 'react-useportal'
Quickstart
import React from 'react';
import usePortal from 'react-useportal';
function App() {
// Basic usage: Portals content to document.body by default
const { Portal: BodyPortal } = usePortal();
// Custom target: Portal content to a specific element
// Ensure 'portal-root' exists in your HTML (e.g., public/index.html)
const { Portal: CustomPortal } = usePortal({
bindTo: typeof document !== 'undefined' ? document.getElementById('portal-root') : null,
// Ensure `document` is available for SSR compatibility
});
// Stateful example with open/close controls
const { Portal: StatefulPortal, openPortal, closePortal, isOpen } = usePortal();
return (
<div>
<h1>React usePortal Examples</h1>
<p>This text is rendered in the main application DOM.</p>
<h2>Stateless Portal to document.body</h2>
<BodyPortal>
<p style={{ border: '2px solid blue', padding: '10px' }}>
This content is portaled to the end of document.body.
</p>
</BodyPortal>
<h2>Custom Target Portal</h2>
<div id="portal-root" style={{ border: '2px dashed green', padding: '10px' }}>
<p>This is the custom target element (e.g., in public/index.html).</p>
<CustomPortal>
<p style={{ border: '2px solid purple', padding: '10px' }}>
This content is portaled into '#portal-root'.
</p>
</CustomPortal>
</div>
<h2>Stateful Portal (Modal Example)</h2>
<button onClick={openPortal}>Open Stateful Portal</button>
{isOpen && (
<StatefulPortal>
<div style={{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'white',
padding: '20px',
border: '1px solid black',
zIndex: 1000,
}}>
<h3>Hello from Stateful Portal!</h3>
<p>You can control its visibility with buttons.</p>
<button onClick={closePortal}>Close Modal</button>
</div>
</StatefulPortal>
)}
</div>
);
}
export default App;