Registry / web-framework / react-reverse-portal

react-reverse-portal

JSON →
library2.3.0jsnpmunverified

React Reverse Portal is a utility library that enables the reparenting of rendered React elements within the DOM without triggering a re-render. Unlike standard React portals which allow an element rendered in one part of the React tree to be physically moved elsewhere in the DOM, reverse portals facilitate pulling an already rendered element from a source location into a target within the React tree. This mechanism is particularly useful for preserving internal React component state and inherent DOM element state (e.g., a playing video) when elements need to be moved, hidden, or reused across different parts of an application. The library is currently at version 2.3.0, actively maintained with a focus on stability and broad React version compatibility (16+). Key differentiators include its small bundle size, zero runtime dependencies, full TypeScript support, and the ability to define props at either the creation or usage location. It's designed for scenarios involving expensive-to-render components that benefit from being instantiated once and then dynamically placed or unplaced.

npm install react-reverse-portal
INSTALL
IMPORT
SIG · REACT-REVERSE-PORT
R
react-reverse-portal
web-frameworkjavascriptv2.3.0
Install
—
Import
—
Disk
—
Pass rate
0/ 6
Env Coverage0 / 6
glibc
18–22
musl
18–22
Install & Compatibility
Where this runs
tested against v? · npm install
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
musl
node 18–226 runs
build_error
glibc
node 18–226 runs
build_error
Code
Verified usage

Verified import paths — ran on the pinned version, not inferred.

createHtmlPortalNode
✓ import { createHtmlPortalNode } from 'react-reverse-portal';
✗ import * as portals from 'react-reverse-portal'; portals.createHtmlPortalNode();
Used to create a stable portal node, typically within `React.useMemo`.
InPortal
✓ import { InPortal } from 'react-reverse-portal';
✗ const { InPortal } = require('react-reverse-portal');
The component that renders content into the portal node. `react-reverse-portal` is ESM-first.
OutPortal
✓ import { OutPortal } from 'react-reverse-portal';
✗ import OutPortal from 'react-reverse-portal';
The component that consumes content from the portal node and displays it. It's a named export.

This quickstart demonstrates creating a stable portal node with `createHtmlPortalNode`, rendering an expensive component into it using `InPortal`, and then moving that same component between two different `OutPortal` locations in the DOM. The `MyExpensiveComponent` maintains its internal state and only renders once, showcasing the library's primary benefit of state preservation during DOM reparenting.

import React from 'react'; import * as portals from 'react-reverse-portal'; // A placeholder for an expensive component const MyExpensiveComponent = ({ data }) => { const [count, setCount] = React.useState(0); React.useEffect(() => { console.log(`MyExpensiveComponent rendered with data: ${data}`); }, [data]); return ( <div style={{ border: '1px solid gray', padding: '10px', margin: '10px' }}> <h3>Expensive Component ({data})</h3> <p>Internal state count: {count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> <p>This component is rendered once and moved around.</p> </div> ); }; const MyComponent = () => { const portalNode = React.useMemo(() => portals.createHtmlPortalNode(), []); const [showInFirstPlace, setShowInFirstPlace] = React.useState(true); return ( <div> <h1>React Reverse Portal Example</h1> <div style={{ background: '#e0ffe0', padding: '15px', margin: '10px' }}> <h2>Source Content (InPortal)</h2> <p>This defines the content that will be moved:</p> <portals.InPortal node={portalNode}> <MyExpensiveComponent data="Source Data" /> </portals.InPortal> <p>The content above is rendered once but not necessarily visible here.</p> </div> <div style={{ background: '#ffe0e0', padding: '15px', margin: '10px' }}> <h2>Destination 1 (OutPortal)</h2> <button onClick={() => setShowInFirstPlace(true)}> Show in Destination 1 </button> {showInFirstPlace && ( <div style={{ border: '2px dashed blue', padding: '10px' }}> <p>Content pulled here:</p> <portals.OutPortal node={portalNode} /> </div> )} </div> <div style={{ background: '#e0e0ff', padding: '15px', margin: '10px' }}> <h2>Destination 2 (OutPortal)</h2> <button onClick={() => setShowInFirstPlace(false)}> Show in Destination 2 </button> {!showInFirstPlace && ( <div style={{ border: '2px dashed green', padding: '10px' }}> <p>Content pulled here:</p> <portals.OutPortal node={portalNode} /> </div> )} </div> </div> ); }; export default MyComponent;
Debug
Known issues
gotchaThe `portalNode` returned by `createHtmlPortalNode()` must be stable across renders. Failing to use `React.useMemo` (or `React.useRef` for a single-time creation) for the portal node will cause the component to lose state or re-render unexpectedly when its parent component re-renders.
fix
Always initialize the `portalNode` within `React.useMemo` or `React.useRef` to ensure its stability across renders: `const portalNode = React.useMemo(() => portals.createHtmlPortalNode(), []);`
affects: >=1.0.0
breakingIncorrect peer dependency versions of `react` or `react-dom` can lead to runtime errors or unexpected behavior. While `react-reverse-portal` supports a wide range, ensure your project's React versions fall within the declared peer dependency range.
fix
Check your project's `package.json` for `react` and `react-dom` versions and ensure they satisfy the peer dependency requirements (`^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0`). Upgrade or downgrade React/ReactDOM if necessary, or use an override/resolution in your package manager.
affects: <2.0.0
gotchaIt's crucial to understand the 'reverse' nature of these portals. Unlike standard `ReactDOM.createPortal` which moves content *out* of its React tree render location, `react-reverse-portal` `InPortal` renders content into a detached DOM node, and `OutPortal` then *pulls* that content into the React tree. Misunderstanding this flow can lead to confusion about where props are applied or where effects run.
fix
Review the official documentation and examples to grasp the 'pull' mechanism. Props can be provided to `InPortal` (initial state) or `OutPortal` (dynamic props, which will trigger re-renders of the content within the `InPortal`).
affects: >=1.0.0
Errors
Common errors & fixes
Error: InPortal: `node` prop is required.
The `InPortal` component was rendered without providing a `node` prop, which is the `PortalNode` created by `createHtmlPortalNode`.
fix
Ensure `InPortal` receives a `node` prop: `<InPortal node={portalNode}>...</InPortal>` where `portalNode` is a stable object created via `React.useMemo(() => createHtmlPortalNode(), [])`.
TypeError: Cannot read properties of undefined (reading 'InPortal')
This typically occurs when attempting to use `InPortal` (or `OutPortal`, `createHtmlPortalNode`) without correctly importing it, often by using CommonJS `require()` syntax with an ESM-first package or incorrect named import destructuring.
fix
Use ES Module imports: `import { InPortal, OutPortal, createHtmlPortalNode } from 'react-reverse-portal';` or `import * as portals from 'react-reverse-portal';`.
Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
While not unique to this library, frequently moving the `OutPortal` without memoizing or stabilizing the context in which it operates can sometimes lead to excessive re-renders if the component inside the `InPortal` has side effects or relies on unstable props/context.
fix
Ensure props passed to `InPortal` or `OutPortal` (if applicable) are stable. If `InPortal`'s content relies on context, verify that context providers are not frequently re-mounting or providing unstable values. The `portalNode` itself *must* be stable.
Upgrade
Version history
2.3.0latest on npm
Audit
Dependencies
reactrequiredPeer dependency required for any React application; specifies supported versions.
react-domrequiredPeer dependency required for rendering React components to the DOM; specifies supported versions.
Agent activity
4 hits · last 30 days
node
4
Resources
react-reverse-portal — npm install react-reverse-portal · libregistry