• Stars
    star
    885
  • Rank 51,582 (Top 2 %)
  • Language
    JavaScript
  • License
    Apache License 2.0
  • Created over 5 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

React reparenting ⚛️ Build an element once, move it anywhere

React-Reverse-Portal

Part of HTTP Toolkit: powerful tools for building, testing & debugging HTTP(S)

Apache 2.0 licensed Tiny bundle size Travis Build Status Available on NPM

Build an element once, move it anywhere

Added in React 16.0, React's built-in portals let you render an element in a meaningful location within your React component hierarchy, but then send the output to a DOM node elsewhere.

Reverse portals let you do the opposite: pull a rendered element from elsewhere into a target location within your React tree. This allows you to reparent DOM nodes, so you can move React-rendered elements around your React tree and the DOM without re-rendering them.

Reverse portals also allow you to take a React-rendered node out of the DOM entirely, and return it later, all without rerendering the node.

(In a rush? Check out the examples)

This is useful in a few cases:

  • Your react elements have internal state, and you'd like to persist that state but render the element somewhere new.
  • Your DOM elements have built-in state (e.g. a playing <video> element), and you'd like to move the element elsewhere without losing that.
  • Your elements are expensive to render, and you'd like to render them once and then place/unplace them later (e.g. a reusable pool of expensive-to-render elements that can be shared among different parts of your application).
  • You want to define the contents of an element separately from where it actually appears in the tree, e.g. modals, breadcrumbs, etc (possible with normal portals, but made more flexible & declarative with reverse portals)

In HTTP Toolkit for example, this is used to render Monaco Editor (an expensive-to-initialize rich text editor) only once, and then quickly & easily reuse the same editor to show the body of many different HTTP requests & responses in different places, without ever having to rebuild the component, making the UI much more responsive. Check out the full diff to implement that here: httptoolkit-ui@8456eeca7a886b2d57b2a84bb4ecf299e20c77f8.

Features

  • Reparent rendered React elements without rerendering
  • Provide props at either the creation (in-portal) or usage (out-portal) locations, or both
  • Supports reparenting of both HTML and SVG elements
  • Single tiny file (2.5kB unminified, ungzipped) with zero dependencies
  • Works with React 16+
  • Written in TypeScript

Getting Started

Install it:

npm install react-reverse-portal

Create a portal node, populate it with InPortal, and use it somewhere with 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 that 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, MyExpensiveComponent, it
            will not appear anywhere on the page.
        */}
        <portals.InPortal node={portalNode}>
            <MyExpensiveComponent
                // Optionally set props to use now, before this enters the DOM
                myProp={"defaultValue"}
            />
        </portals.InPortal>

        {/* ... The rest of your UI ... */}

        {/* Later, pass the portal node around to whoever might want to use it: */}
        { props.componentToShow === 'component-a'
            ? <ComponentA portalNode={portalNode} />
            : <ComponentB portalNode={portalNode} /> }
    </div>;
}

// Later still, pull content from the portal node and show it somewhere:

const ComponentA = (props) => {
    return <div>
        {/* ... Some more UI ... */}

        {/* Show the content of the portal node here: */}
        <portals.OutPortal node={props.portalNode} />
    </div>;
}

const ComponentB = (props) => {
    return <div>
        {/* ... Some more UI ... */}

        <portals.OutPortal
            node={props.portalNode}
            myProp={"newValue"}     // Optionally, override default values
            myOtherProp={123}       // Or add new props

            // These props go back to the content of the InPortal, and trigger a
            // component render (but on the same component instance) as if they
            // had been passed to MyExpensiveComponent directly.
        />
    </div>;
}

Check out the examples to see this in action

What just happened?

React-reverse-portal lets you render a node once, and then place it elsewhere, without being recreated from scratch.

Normally in ComponentA/ComponentB examples like the above, switching from ComponentA to ComponentB would cause every child component to be rebuilt, due to how React's DOM diffing & reconciliation works. Here, they're not, and you can reparent however you'd like.

How does it work under the hood?

portals.createHtmlPortalNode([options])

This creates a detached DOM node, with a little extra functionality attached to allow transmitting props later on.

This node will contain your portal contents later, within a <div>, and will eventually be attached in the target location.

An optional options object parameter can be passed to configure the node. The only supported option is attributes: this can be used to set the HTML attributes (style, class, etc.) of the intermediary, like so:

const portalNode = portals.createHtmlPortalNode({
	attributes: { id: "div-1", style: "background-color: #aaf; width: 100px;" }
});

The div's DOM node is also available at .element, so you can mutate that directly with the standard DOM APIs if preferred.

portals.createSvgPortalNode([options])

This creates a detached SVG DOM node. It works identically to the node from createHtmlPortalNode, except it will work with SVG elements. Content is placed within a <g> instead of a <div>.

An error will be thrown if you attempt to use a HTML node for SVG content, or a SVG node for HTML content.

portals.InPortal

InPortal components take a node prop for the portal node, along with React children that will be rendered.

It reads any extra props for the children from the live OutPortals if any (via the node itself), and then renders the children (using these extra props if available) into the detached DOM node. At this point, the children are mounted & rendered React nodes, but living entirely outside the page DOM.

All extra props are given to all children of the InPortal (except strings/null). If you need to give different props to different children, create a wrapper component to do so.

portals.OutPortal

OutPortal components take a node for the portal node, and any extra props that should be passed through to the portal children.

It extra props to the InPortal (using the extra functionality we've attached to the portal node), and when rendered it attaches the detached DOM node in its place in the DOM. More specifically: the DOM node returned by create*PortalNode will be attached in the place of <portals.OutPortal> in your React tree.

Important notes

  • Nodes should be rendered to at most one OutPortal at any time. Rendering a node in two OutPortals simultaneously will make bad things happen, rendering the node in a random one or maybe none of the OutPortals, and probably setting up React to explode later. Each portal node should only be used in one place at any time.
  • Rendering to zero OutPortals is fine: the node will be rendered as normal, using just the props provided inside the InPortal definition, and will continue to happily exist but just not appear in the DOM anywhere.
  • Reverse portals tie rebuilding of the contents of the portal to InPortal (where it's defined), rather than the parent of the OutPortal (where it appears in the tree). That's great (that's the whole point really), but the contents of the InPortal will still be rebuilt anytime the InPortal itself is, e.g. if the InPortal's parent is rebuilt.
  • Be aware that if you call create*PortalNode in the render method of the parent of an InPortal, you'll get a different node to render into each time, and this will cause unnecessary rerenders, one every time the parent updates. It's generally better to create the node once and persist it, either using the useMemo hook, or in the initial construction of your class component.
  • By default, the types for nodes, InPortals and OutPortals allow any props and any components. Pass a component type to them to be more explicit and enforce prop types, e.g. createHtmlPortalNode<MyComponent>, <portals.InPortal<MyComponent>>, <portals.OutPortal<MyComponent>>.

Contributing

Like this project and want to help?

For starters, open source projects live on Github stars: star this repo and share the love

Bug reports & questions

Want to ask a question? Feel free; just file an issue.

Hitting a bug? File an issue, just make sure to include the React & ReactDOM versions you're using, the browser that's showing the issue, and a clear example so we can reproduce the problem.

Feature suggestions & changes

Want to contribute changes directly, to add features, fix issues or document better? Great! Contributions from everybody are very welcome. If your change is small and you're pretty sure it'll be clearly wanted feel free to just open a PR directly. If you're not 100% sure, you're always welcome to open an issue and ask!

The structure in the codebase is fairly simple:

To actually make your changes, you just need to set up the codebase:

  • npm install
  • npm run build - compiles the code
  • npm test - no tests for now, so this just checks the code compiles (it's the same as build). Tests welcome!
  • npm run storybook - starts up the storybook and opens it in your browser. You'll probably need to npm run build first, and again later if you make changes in src/.

More Repositories

1

httptoolkit

HTTP Toolkit is a beautiful & open-source tool for debugging, testing and building with HTTP(S) on Windows, Linux & Mac 🎉 Open an issue here to give feedback or ask for help.
2,730
star
2

frida-interception-and-unpinning

Frida scripts to directly MitM all HTTPS traffic from a target mobile application
JavaScript
1,088
star
3

mockttp

Powerful friendly HTTP mock server & proxy library
TypeScript
779
star
4

httptoolkit-desktop

Electron wrapper to build and distribute HTTP Toolkit for the desktop
TypeScript
617
star
5

httptoolkit-android

Automatic Android interception & debugging with HTTP Toolkit, for Android
Java
480
star
6

httptoolkit-server

The backend of HTTP Toolkit
JavaScript
452
star
7

httptoolkit-ui

The UI of HTTP Toolkit
TypeScript
292
star
8

mockrtc

Powerful friendly WebRTC mock peer & proxy
TypeScript
282
star
9

brotli-wasm

A reliable compressor and decompressor for Brotli, supporting node & browsers via wasm
TypeScript
262
star
10

android-ssl-pinning-demo

A tiny demo Android app using SSL pinning to block HTTPS MitM interception
Kotlin
120
star
11

httptoolkit-website

The main website of HTTP Toolkit: beautiful, cross-platform & open-source tools to debug, test and develop with HTTP(S).
MDX
74
star
12

jvm-http-proxy-agent

A JVM agent that automatically forces a proxy for HTTP(S) connections and trusts MitM certificates, for all major JVM HTTP clients
Java
69
star
13

docker-registry-facade

A tiny self-hostable Docker Registry facade - own your image URL without running your own registry
Dockerfile
48
star
14

read-tls-client-hello

A pure-JS module to read TLS client hello data and calculate TLS fingerprints from an incoming socket connection.
TypeScript
40
star
15

mockipfs

Powerful friendly IPFS mock node & proxy
TypeScript
39
star
16

docker-socks-tunnel

A tiny Dockerized SOCKS5 proxy
Dockerfile
32
star
17

mockthereum

Powerful friendly Ethereum mock node & proxy
TypeScript
26
star
18

mockttp-proxy-demo

A tiny demo, showing how to build your own scriptable HTTPS-intercepting proxy with Mockttp
JavaScript
23
star
19

frida-js

Pure-JS bindings to control Frida from node.js & browsers
TypeScript
21
star
20

openapi-directory-js

Building & bundling https://github.com/APIs-guru/openapi-directory for easy use from JS
TypeScript
19
star
21

mobx-shallow-undo

Zero-config undo & redo for Mobx
TypeScript
18
star
22

browser-launcher

Detect the browser versions available on your system, and launch them in an isolated profile for automation & testing purposes.
JavaScript
18
star
23

httpolyglot

Serve http and https connections over the same port with node.js
TypeScript
15
star
24

accounts

The API & dashboard that power HTTP Toolkit account management
TypeScript
10
star
25

mockrtc-extension-example

An example web extension, using MockRTC to intercept & debug your own WebRTC traffic
TypeScript
10
star
26

ios-ssl-pinning-demo

A tiny demo iOS app using SSL pinning to block HTTPS MitM interception
Swift
9
star
27

httpsnippet

HTTP Request snippet generator for many languages & libraries
JavaScript
8
star
28

usbmux-client

A pure-js Node.js library for communicating with iPhones over USB via usbmux
TypeScript
8
star
29

os-proxy-config

Access the operating system proxy configuration from Node.js, for all platforms
TypeScript
6
star
30

http-encoding

Everything you need to handle HTTP message body content-encoding
TypeScript
5
star
31

anonymizing-reverse-proxy

Anonymizing reverse proxy used between HTTP Toolkit end users & 3rd party services
Dockerfile
5
star
32

mac-system-proxy

Access the Mac system proxy settings from Node.js
TypeScript
5
star
33

xz-decompress

XZ decompression for the browser & Node without native code, via WebAssembly
JavaScript
5
star
34

webextension

A browser extension used in HTTP Toolkit
TypeScript
4
star
35

osx-find-executable

Find an app's executable by its bundle id
JavaScript
4
star
36

node-launcher

WIP: An node.js-powered launcher for httptoolkit (try 'npx httptoolkit')
JavaScript
3
star
37

websocket-stream

websockets with the node stream API
JavaScript
3
star
38

act-build-base

A base image for local GitHub Action builds with Act
Shell
3
star
39

demo-scripts

WIP: an script for automatically following (and recording) workflows in HTTP Toolkit
TypeScript
3
star
40

android.httptoolkit.tech

Static site used as infrastructure to support the HTTP Toolkit Android app
2
star
41

evil-package

An npm package demonstrating how packages can steal your data (but not actually doing so!)
JavaScript
2
star
42

windows-system-proxy

Access the Windows system proxy settings from Node.js.
TypeScript
2
star
43

ipfs-openapi-spec

An IPFS OpenAPI spec, automatically generated from the official documentation
TypeScript
2
star
44

testserver

A public test server for HTTP & related protocols, similar to httpbin.org (but actively maintained)
TypeScript
2
star
45

destroyable-server

A tiny Node.js module to make any net.Server force-closeable
TypeScript
1
star
46

amiusing

Microsite to tell you if you're currently being proxied by HTTP Toolkit
HTML
1
star
47

statuspagestatuspage

TypeScript
1
star