Tim Perry
Tim Perry

Reputation: 13256

How to create a reverse react portal

React portals let you render a React child into a different DOM element, somewhere totally separate on the page.

I want to do the reverse: I want to render a DOM element once, elsewhere on the page, and then pull it into my React DOM output depending on my react tree.

The specific use case for this is Monaco Editor. It's a complex component, which is expensive to render from scratch, but cheap to update. In my application, the editor appears/disappears as users open/close different content. That means React creates the component from scratch each time new content is opened, and the delay to create a new editor makes that action a slower than I'd like.

I'd like to render it once, hide it somewhere, and then show it in the right place in my DOM when & where it's needed. I know that I only need one instance at any time.

Upvotes: 8

Views: 1887

Answers (1)

Tim Perry
Tim Perry

Reputation: 13256

In the end I built a library to do it myself: https://github.com/httptoolkit/react-reverse-portal.

Render your content in one place, once, and pass it around and insert it into the DOM somewhere else without rerendering.

Example:

import * as portals from 'react-reverse-portal';

const MyComponent = (props) => {
    const portalNode = React.useMemo(() => portals.createPortalNode());

    return <div>
        <portals.InPortal node={portalNode}>
            <MyExpensiveComponent
                myProp={"defaultValue"}
            />
        </portals.InPortal>

        { props.componentToShow === 'component-a'
            ? <ComponentA portalNode={portalNode} />
            : <ComponentB portalNode={portalNode} /> }
    </div>;
}

const ComponentA = (props) => {
    return <div>
        A:
        <portals.OutPortal node={props.portalNode} />
    </div>;
}

const ComponentB = (props) => {
    return <div>
        B:
        <portals.OutPortal
            node={props.portalNode}
            myProp={"newValue"}
            myOtherProp={123}
        />
    </div>;
}

Upvotes: 15

Related Questions