Move Rendered React Elements Around Without Re-Rendering Them
You've probably hit this at some point: you have a component with internal state you want to keep, but you need it to show up somewhere else in the tree. Or maybe you've got an expensive editor or a playing <video> element, and moving it means losing everything it was doing. React's built-in portals solve half of this problem. React-Reverse-Portal solves the other half.
What It Does
React-Reverse-Portal is a small library that lets you pull a rendered element from elsewhere into a target location within your React tree. Where a normal portal renders an element in your component hierarchy but sends the output to a DOM node somewhere else, reverse portals do the opposite: they let you reparent DOM nodes so you can move React-rendered elements around your tree and the DOM without triggering a re-render.
You create a portal node, populate it with an InPortal, and then place it wherever you need it using an OutPortal. The content renders immediately when it's created, but it doesn't appear on the page until you use the portal node in an OutPortal. You can also take a React-rendered node out of the DOM entirely and return it later, all without re-rendering.
The library is written in TypeScript, works with React 16 and up, supports both HTML and SVG elements, and ships as a single tiny file (2.5kB unminified, ungzipped) with zero dependencies.
Why It's Cool
The core appeal here is state preservation. If your React elements have internal state, you can keep that state alive while rendering the element somewhere new. If your DOM elements have built-in state (a playing <video> is the classic example), you can move them without losing it.
-
Render once, reuse often. Expensive-to-render elements can be created a single time and then placed or unplaced as needed. This is exactly how HTTP Toolkit uses it: they render Monaco Editor once and reuse the same instance to show the body of many different HTTP requests and responses in different places, without ever rebuilding the component. That's a meaningful UI responsiveness win, and it's a real production use case, not a hypothetical.
-
Props are flexible. You can provide props at either the creation location (the
InPortal) or the usage location (theOutPortal), or both. That's a nice bit of design—it means the component definition and its actual placement don't have to be tightly coupled. -
It's small and boring in the best way. No dependencies, one file, TypeScript types included. For a library that does something fairly subtle to React's rendering model, that's a good sign.
-
Good fit for modals and breadcrumbs. The README notes you can define the contents of an element separately from where it appears in the tree. Normal portals can do this too, but reverse portals make it more flexible and declarative.
How to Try It
Install it from npm:
npm install react-reverse-portal
Then create a portal node, fill it with an InPortal, and place it with an OutPortal:
import * as portals from 'react-reverse-portal';
const MyComponent = (props) => {
// Create a portal node: this holds your rendered content
const portalNode = React.useMemo(() => portals.createHtmlPortalNode(), []);
return <div>
{/*
Render the content you want to move around later.
InPortals render as normal, but send the output to detached DOM.
MyExpensiveComponent will be rendered immediately, but until
portalNode is used in an OutPortal, it will not appear anywhere.
*/}
<portals.InPortal node={portalNode}>
<MyExpensiveComponent />
</portals.InPortal>
</div>;
};
The README includes a full example, and there are more in the project's examples page. The repository lives at github.com/httptoolkit/react-reverse-portal, and it's part of HTTP Toolkit, which builds tools for testing and debugging HTTP(S).
Final Thoughts
React-Reverse-Portal is a focused solution to a specific problem, and it's honest about that. If your components are cheap to render and stateless, you probably don't need it. But if you're juggling expensive components, stateful DOM elements, or a UI where the same element needs to appear in different places without losing its mind—this is a clean, dependency-free way to handle it. Worth a look if you've ever found yourself wishing a portal worked in reverse.