React library that simplifies working with React Portals by providing Portal and PortalWithState components for breaking out of normal DOM hierarchy.
npx @tessl/cli install tessl/npm-react-portal@4.3.0React Portal is a React library that simplifies working with React Portals by providing two main components: Portal and PortalWithState. The Portal component transports children into a new React Portal (typically appended to document.body) and supports targeting custom DOM elements, server-side rendering, and returning arrays without wrapper divs. The PortalWithState component adds convenience by managing its own state and providing features like close-on-ESC, close-on-outside-click, and callback functions for open/close events.
npm install react-portal or yarn add react-portalimport { Portal, PortalWithState } from 'react-portal';For CommonJS:
const { Portal, PortalWithState } = require('react-portal');import React from 'react';
import { Portal, PortalWithState } from 'react-portal';
// Basic Portal usage
const MyModal = ({ isOpen, children }) => (
isOpen && <Portal>{children}</Portal>
);
// PortalWithState usage with built-in controls
const AdvancedModal = () => (
<PortalWithState closeOnOutsideClick closeOnEsc>
{({ openPortal, closePortal, isOpen, portal }) => (
<React.Fragment>
<button onClick={openPortal}>Open Modal</button>
{portal(
<div style={{ background: 'white', padding: '20px' }}>
<h2>Modal Content</h2>
<button onClick={closePortal}>Close</button>
</div>
)}
</React.Fragment>
)}
</PortalWithState>
);React Portal is built around React's Portal API with the following key components:
ReactDOM.createPortal and legacy unstable_renderSubtreeIntoContainerCore portal component that transports React elements into a React Portal, breaking out of normal DOM hierarchy for proper styling and positioning.
/**
* Portal component that renders children into a React Portal
*/
class Portal extends React.Component<PortalProps> {}
interface PortalProps {
/** React elements to be portaled */
children: React.ReactNode;
/** Optional target DOM element (defaults to auto-created div in document.body) */
node?: HTMLElement | Element | null;
}Usage Examples:
// Portal to document.body (default)
<Portal>
<div>This content appears at the end of document.body</div>
</Portal>
// Portal to custom element
const customTarget = document.getElementById('modal-root');
<Portal node={customTarget}>
<div>This content appears in the custom element</div>
</Portal>
// Conditional portal
{isModalOpen && (
<Portal>
<div className="modal-overlay">
<div className="modal-content">Modal content here</div>
</div>
</Portal>
)}Advanced portal component with built-in state management, event handling, and render prop pattern for complete portal control.
/**
* Stateful portal component using render prop pattern
*/
class PortalWithState extends React.Component<PortalWithStateProps> {}
interface PortalWithStateProps {
/** Render prop function receiving portal controls */
children: (controls: PortalControls) => React.ReactNode;
/** Initial open state (default: false) */
defaultOpen?: boolean;
/** Target DOM element for portal */
node?: HTMLElement | Element | null;
/** Close portal when ESC key is pressed */
closeOnEsc?: boolean;
/** Close portal when clicking outside */
closeOnOutsideClick?: boolean;
/** Callback fired when portal opens (default: empty function) */
onOpen?: () => void;
/** Callback fired when portal closes (default: empty function) */
onClose?: () => void;
}
interface PortalControls {
/** Function to open the portal */
openPortal: (event?: React.SyntheticEvent) => void;
/** Function to close the portal */
closePortal: () => void;
/** Function that wraps content to be portaled */
portal: (children: React.ReactNode) => React.ReactNode;
/** Current open/closed state */
isOpen: boolean;
}Usage Examples:
// Basic modal with ESC and outside click handling
<PortalWithState closeOnOutsideClick closeOnEsc>
{({ openPortal, closePortal, isOpen, portal }) => (
<React.Fragment>
<button onClick={openPortal}>Open Modal</button>
{portal(
<div className="modal-backdrop">
<div className="modal">
<h2>Modal Title</h2>
<p>Modal content goes here</p>
<button onClick={closePortal}>Close</button>
</div>
</div>
)}
</React.Fragment>
)}
</PortalWithState>
// Modal with callbacks and custom target
<PortalWithState
closeOnEsc
onOpen={() => console.log('Modal opened')}
onClose={() => console.log('Modal closed')}
node={document.getElementById('overlay-root')}
>
{({ openPortal, closePortal, isOpen, portal }) => (
<div>
<button onClick={openPortal}>
{isOpen ? 'Close' : 'Open'} Lightbox
</button>
{portal(
<div className="lightbox">
<img src="/image.jpg" alt="Large image" />
<button onClick={closePortal}>×</button>
</div>
)}
</div>
)}
</PortalWithState>
// Loading overlay that starts open
<PortalWithState defaultOpen>
{({ closePortal, portal }) =>
portal(
<div className="loading-overlay">
<div className="spinner">Loading...</div>
<button onClick={closePortal}>Cancel</button>
</div>
)
}
</PortalWithState>React Portal automatically handles React version compatibility:
ReactDOM.createPortal() (modern approach)ReactDOM.unstable_renderSubtreeIntoContainer() (legacy approach)null safelyReact Portal handles cleanup automatically: