{"id":11829,"library":"react-portal","title":"React Portal Utility","description":"react-portal simplifies the creation and management of React Portals, enabling developers to render children into a different part of the DOM tree, outside the parent component's hierarchy. This is particularly useful for modals, tooltips, lightboxes, and notifications that require specific positioning or need to break out of CSS `overflow: hidden` containers. The current stable version is 4.3.0. It leverages React's official Portal API (introduced in React 16) to provide a robust solution. A key differentiator is its dual component approach, offering both a low-level `<Portal />` for maximum control and a `<PortalWithState />` for common stateful interactions (e.g., close on ESC, close on outside click) without external dependencies. Notably, since v4.1.0, it includes a fallback mechanism to support React v15 while primarily targeting React v16 and newer, making it flexible for diverse project ecosystems. It focuses on clean markup, SSR compatibility, and minimalistic design. The project demonstrates an active maintenance cadence, with recent minor releases addressing bugs and ensuring compatibility.","status":"active","version":"4.3.0","language":"javascript","source_language":"en","source_url":"https://github.com/tajo/react-portal","tags":["javascript","react","react-component","modal","lightbox","react-portal","portal","transportation"],"install":[{"cmd":"npm install react-portal","lang":"bash","label":"npm"},{"cmd":"yarn add react-portal","lang":"bash","label":"yarn"},{"cmd":"pnpm add react-portal","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Core library for building user interfaces, required by all React components.","package":"react","optional":false},{"reason":"Provides DOM-specific rendering methods, essential for mounting React components to the DOM and utilizing `createPortal`.","package":"react-dom","optional":false}],"imports":[{"note":"Primary component for creating basic portals without state management. While `require` works, modern React usage strongly prefers ESM imports.","wrong":"const { Portal } = require('react-portal');","symbol":"Portal","correct":"import { Portal } from 'react-portal';"},{"note":"Higher-order component that manages its own open/closed state, including close on ESC and outside click. Direct imports from 'lib' are discouraged and brittle.","wrong":"import PortalWithState from 'react-portal/lib/PortalWithState';","symbol":"PortalWithState","correct":"import { PortalWithState } from 'react-portal';"},{"note":"TypeScript users can import specific prop types for better type checking and IntelliSense.","symbol":"Types","correct":"import type { PortalProps, PortalWithStateProps } from 'react-portal';"}],"quickstart":{"code":"import React, { useState, useEffect } from 'react';\nimport { Portal, PortalWithState } from 'react-portal';\nimport { createRoot } from 'react-dom/client';\n\nconst App = () => {\n  const [isBasicPortalOpen, setIsBasicPortalOpen] = useState(false);\n\n  // For demonstration purposes, create a target node if it doesn't exist\n  useEffect(() => {\n    if (typeof document !== 'undefined' && !document.getElementById('my-custom-portal-target')) {\n      const div = document.createElement('div');\n      div.id = 'my-custom-portal-target';\n      document.body.appendChild(div);\n    }\n  }, []);\n\n  return (\n    <div>\n      <h1>React-Portal Example</h1>\n\n      <h2>Basic Portal</h2>\n      <button onClick={() => setIsBasicPortalOpen(!isBasicPortalOpen)}>\n        Toggle Basic Portal ({isBasicPortalOpen ? 'Open' : 'Closed'})\n      </button>\n      {isBasicPortalOpen && (\n        <Portal>\n          <div style={{\n            position: 'fixed',\n            top: '50%',\n            left: '50%',\n            transform: 'translate(-50%, -50%)',\n            background: 'white',\n            border: '2px solid blue',\n            padding: '20px',\n            zIndex: 1000,\n            boxShadow: '0 4px 8px rgba(0,0,0,0.1)'\n          }}>\n            <p>This is a basic portal injected into <code>document.body</code>.</p>\n            <button onClick={() => setIsBasicPortalOpen(false)}>Close</button>\n          </div>\n        </Portal>\n      )}\n\n      <h2>PortalWithState</h2>\n      <PortalWithState closeOnOutsideClick closeOnEsc>\n        {({ openPortal, closePortal, isOpen, portal }) => (\n          <React.Fragment>\n            <button onClick={openPortal} disabled={isOpen}>\n              {isOpen ? 'Portal Open' : 'Open Advanced Portal'}\n            </button>\n            {portal(\n              <div style={{\n                position: 'fixed',\n                top: '60%',\n                left: '50%',\n                transform: 'translate(-50%, -50%)',\n                background: 'lightgreen',\n                border: '2px solid darkgreen',\n                padding: '25px',\n                zIndex: 1001,\n                boxShadow: '0 6px 12px rgba(0,0,0,0.15)'\n              }}>\n                <p>\n                  This is a more advanced Portal. It handles its own state.\n                  <br />\n                  <button onClick={closePortal}>Close me!</button>, hit ESC or\n                  click outside of me.\n                </p>\n                <p>Status: {isOpen ? 'Open' : 'Closed'}</p>\n              </div>\n            )}\n          </React.Fragment>\n        )}\n      </PortalWithState>\n\n      <h2>Custom Node Portal</h2>\n      <p>This portal targets a custom div with id 'my-custom-portal-target'.</p>\n      <Portal node={document && document.getElementById('my-custom-portal-target')}>\n        <div style={{ border: '1px dashed orange', padding: '10px', marginTop: '10px' }}>\n          Content portaled to #my-custom-portal-target.\n        </div>\n      </Portal>\n    </div>\n  );\n};\n\n// Mount the App to a root element\nif (typeof document !== 'undefined') {\n  const rootElement = document.getElementById('root');\n  if (!rootElement) {\n    const newRoot = document.createElement('div');\n    newRoot.id = 'root';\n    document.body.appendChild(newRoot);\n  }\n  const root = createRoot(document.getElementById('root')!); // Non-null assertion is safe after check\n  root.render(<App />);\n}","lang":"typescript","description":"Demonstrates both the basic `Portal` and the stateful `PortalWithState` components, showing how to render content into `document.body` or a custom DOM node, and handle portal visibility and interactions like closing on ESC or outside clicks."},"warnings":[{"fix":"Upgrade your project's React and React-DOM versions to 16.0.0 or newer. If maintaining React 15 support is critical, ensure `react-portal` is at least v4.1.0.","message":"Version 4.0.0 was a complete rewrite, dropping support for React versions older than 16.0.0, as it fully adopted the official `ReactDOM.createPortal` API. While v4.1.0 added a fallback for React 15, initial upgrade to 4.0.0 required React 16+.","severity":"breaking","affected_versions":"4.0.0"},{"fix":"Wrap the children of `<Portal>` or `<PortalWithState>` in your own `div` or other element, and apply styling to this wrapper element instead.","message":"Since version 3.0.0, direct styling (inline styles or `className`) on the main `react-portal` component's root div is no longer supported. The component is intended to be unstyled to avoid DOM clutter.","severity":"breaking","affected_versions":">=3.0.0"},{"fix":"Adjust your CSS selectors and component structure if you relied on the `openByClickOn` wrapper div or its className for styling or layout. The clicked element will now appear directly in the DOM.","message":"In version 2.0.0, the element designated by the `openByClickOn` prop is no longer wrapped by an unnecessary `div` element, and the `openByClickOn` className was removed. This was done to provide cleaner markup, particularly for inline elements like buttons.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Structure your `PortalWithState` like this: `<PortalWithState>{({ portal }) => <div>{portal(<p>My content</p>)}</div>}</PortalWithState>`. Do not return the render prop object directly.","message":"When using `PortalWithState`, ensure the single child is a function that returns actual React elements. The content to be portaled must be explicitly passed to the `portal` render prop function.","severity":"gotcha","affected_versions":">=4.0.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Upgrade `react` and `react-dom` to at least version 16.0.0. If you must support React 15, upgrade `react-portal` to v4.1.0 or later for its fallback mechanism.","cause":"Attempting to use `react-portal` v4.0.0 with a React version older than 16.0.0.","error":"TypeError: ReactDOM.createPortal is not a function"},{"fix":"Ensure `Portal` components are only rendered on the client-side, or use conditional rendering based on `typeof document !== 'undefined'` for any custom logic that interacts with the DOM.","cause":"Accessing the `document` object or rendering `react-portal` components directly in a Server-Side Rendering (SSR) environment without client-side checks.","error":"ReferenceError: document is not defined"},{"fix":"The function child of `PortalWithState` must return React elements, and the actual content to be portaled should be passed as an argument to the `portal` render prop function. For example: `{({ openPortal, portal }) => (<button onClick={openPortal}>{portal(<p>Content</p>)}</button>)}`.","cause":"Incorrect usage of `PortalWithState` where its function child returns the render props object directly instead of calling the `portal` function with the content to be portaled.","error":"Objects are not valid as a React child (found: object with keys {openPortal, closePortal, isOpen, portal}). If you meant to render a collection of children, use an array instead."}],"ecosystem":"npm"}