{"id":13901,"library":"react-reverse-portal","title":"React Reverse Portal","description":"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.","status":"active","version":"2.3.0","language":"javascript","source_language":"en","source_url":"https://github.com/httptoolkit/react-reverse-portal","tags":["javascript","react","portal","move","reparent","container","wormhole","dom","typescript"],"install":[{"cmd":"npm install react-reverse-portal","lang":"bash","label":"npm"},{"cmd":"yarn add react-reverse-portal","lang":"bash","label":"yarn"},{"cmd":"pnpm add react-reverse-portal","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Peer dependency required for any React application; specifies supported versions.","package":"react","optional":false},{"reason":"Peer dependency required for rendering React components to the DOM; specifies supported versions.","package":"react-dom","optional":false}],"imports":[{"note":"Used to create a stable portal node, typically within `React.useMemo`.","wrong":"import * as portals from 'react-reverse-portal'; portals.createHtmlPortalNode();","symbol":"createHtmlPortalNode","correct":"import { createHtmlPortalNode } from 'react-reverse-portal';"},{"note":"The component that renders content into the portal node. `react-reverse-portal` is ESM-first.","wrong":"const { InPortal } = require('react-reverse-portal');","symbol":"InPortal","correct":"import { InPortal } from 'react-reverse-portal';"},{"note":"The component that consumes content from the portal node and displays it. It's a named export.","wrong":"import OutPortal from 'react-reverse-portal';","symbol":"OutPortal","correct":"import { OutPortal } from 'react-reverse-portal';"}],"quickstart":{"code":"import React from 'react';\nimport * as portals from 'react-reverse-portal';\n\n// A placeholder for an expensive component\nconst MyExpensiveComponent = ({ data }) => {\n  const [count, setCount] = React.useState(0);\n  React.useEffect(() => {\n    console.log(`MyExpensiveComponent rendered with data: ${data}`);\n  }, [data]);\n  return (\n    <div style={{ border: '1px solid gray', padding: '10px', margin: '10px' }}>\n      <h3>Expensive Component ({data})</h3>\n      <p>Internal state count: {count}</p>\n      <button onClick={() => setCount(c => c + 1)}>Increment</button>\n      <p>This component is rendered once and moved around.</p>\n    </div>\n  );\n};\n\nconst MyComponent = () => {\n  const portalNode = React.useMemo(() => portals.createHtmlPortalNode(), []);\n  const [showInFirstPlace, setShowInFirstPlace] = React.useState(true);\n\n  return (\n    <div>\n      <h1>React Reverse Portal Example</h1>\n\n      <div style={{ background: '#e0ffe0', padding: '15px', margin: '10px' }}>\n        <h2>Source Content (InPortal)</h2>\n        <p>This defines the content that will be moved:</p>\n        <portals.InPortal node={portalNode}>\n          <MyExpensiveComponent data=\"Source Data\" />\n        </portals.InPortal>\n        <p>The content above is rendered once but not necessarily visible here.</p>\n      </div>\n\n      <div style={{ background: '#ffe0e0', padding: '15px', margin: '10px' }}>\n        <h2>Destination 1 (OutPortal)</h2>\n        <button onClick={() => setShowInFirstPlace(true)}>\n          Show in Destination 1\n        </button>\n        {showInFirstPlace && (\n          <div style={{ border: '2px dashed blue', padding: '10px' }}>\n            <p>Content pulled here:</p>\n            <portals.OutPortal node={portalNode} />\n          </div>\n        )}\n      </div>\n\n      <div style={{ background: '#e0e0ff', padding: '15px', margin: '10px' }}>\n        <h2>Destination 2 (OutPortal)</h2>\n        <button onClick={() => setShowInFirstPlace(false)}>\n          Show in Destination 2\n        </button>\n        {!showInFirstPlace && (\n          <div style={{ border: '2px dashed green', padding: '10px' }}>\n            <p>Content pulled here:</p>\n            <portals.OutPortal node={portalNode} />\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default MyComponent;","lang":"typescript","description":"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."},"warnings":[{"fix":"Always initialize the `portalNode` within `React.useMemo` or `React.useRef` to ensure its stability across renders: `const portalNode = React.useMemo(() => portals.createHtmlPortalNode(), []);`","message":"The `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.","severity":"gotcha","affected_versions":">=1.0.0"},{"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.","message":"Incorrect 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.","severity":"breaking","affected_versions":"<2.0.0"},{"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`).","message":"It'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.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"search_vec":"'16':125 '2.3.0':112 'abil':141 'across':100 'activ':113 'allow':33 'alreadi':57 'applic':105 'benefit':164 'broad':121 'bundl':131 'compat':124 'compon':80,162 'contain':180 'creation':148 'current':109 'defin':143 'depend':135 'design':154 'differ':101 'differenti':127 'dom':21,51,84,182 'dynam':171 'e.g':87 'either':146 'element':18,35,59,85,92 'elsewher':48 'enabl':12 'expens':159 'expensive-to-rend':158 'facilit':54 'focus':117 'full':136 'hidden':97 'includ':128 'inher':83 'instanti':167 'intern':78 'involv':157 'javascript':175 'key':126 'librari':10,107 'locat':63,151 'maintain':114 'mechan':72 'move':47,96,178 'need':93 'one':38 'part':39,102 'particular':74 'physic':46 'place':172 'play':89 'portal':3,6,31,53,177 'preserv':77 'prop':144 'pull':55 're':26 're-rend':25 'react':1,4,17,30,42,69,79,122,176 'render':16,27,36,58,161 'repar':14,179 'reus':99 'revers':2,5,52 'runtim':134 'scenario':156 'size':132 'small':130 'sourc':62 'stabil':119 'standard':29 'state':81,86 'support':138 'target':66 'tree':43,70 'trigger':23 'typescript':137,183 'unlik':28 'unplac':174 'usag':150 'use':75 'util':9 'version':111,123 'video':90 'within':19,67 'without':22 'wormhol':181 'zero':133","created_at":"2026-04-20T01:56:54.046830+00:00","updated_at":"2026-04-20T01:56:54.046830+00:00","problems":[{"fix":"Ensure `InPortal` receives a `node` prop: `<InPortal node={portalNode}>...</InPortal>` where `portalNode` is a stable object created via `React.useMemo(() => createHtmlPortalNode(), [])`.","cause":"The `InPortal` component was rendered without providing a `node` prop, which is the `PortalNode` created by `createHtmlPortalNode`.","error":"Error: InPortal: `node` prop is required."},{"fix":"Use ES Module imports: `import { InPortal, OutPortal, createHtmlPortalNode } from 'react-reverse-portal';` or `import * as portals from 'react-reverse-portal';`.","cause":"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.","error":"TypeError: Cannot read properties of undefined (reading 'InPortal')"},{"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.","cause":"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.","error":"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."}],"ecosystem":"npm","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":null,"cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/httptoolkit/react-reverse-portal","docs":null,"changelog":null,"pypi":null,"npm":"https://www.npmjs.com/package/react-reverse-portal","openapi_spec":null,"status_page":null,"smithery":null,"categories":["web-framework"],"base_url":null,"auth_type":null,"provenance":{"verified_status":null,"verified_at":null,"last_verified":"2026-06-17","next_check":"2026-07-18","install_tag":null}}