10

Lots of new functions released with React 16. One of them is the ReactDOM.createPortal(child, container) API, which is handy for visually breaking out of its container.

However, it seems like that it not only breaks out its container but also breaks the basic html rules which I learned from the first day of web development. The createPortal API let you render your component out of its parent, and break the html structure convention we expected.

In the other hand, we do retrieve more flexibility and now can render DOM in the sibling or else components.

IMO, I don't think this is a good deal to gain more flexibility by trading html convention in. Also the example supplied by official does not convince me.

What I am curious about is: Is there anyone who face any condition that createPortal API is a must?

thanks

fung
  • 641
  • 6
  • 14

4 Answers4

12

The examples in the docs are some of the cases where createPortal is really useful - specifically dialogs, hovercards, and tooltips.

The docs also specifically state:

Note:

It is important to remember, when working with portals, you’ll need to make sure to follow the proper accessibility guidelines.

As an example, the docs show how a modal could be built using createPortal(). You'll notice the modal is created in the #modal-root element, which is a root element alongside the #app-root element. This is a great example of how createPortal() can be used without violating any HTML rules.

<div id="app-root"></div>
<div id="modal-root"></div>
Community
  • 1
  • 1
Brett DeWoody
  • 59,771
  • 29
  • 135
  • 184
  • Can you explain also why this is an example of where you MUST (see OP question) use a portal? There are numerous modal implementations that don't use a portal, surely a portal is not the best, let alone the only, option. – inwerpsel Jul 14 '22 at 09:35
  • From the perspective of accessibility, I would say using a Portal for a modal is a must. Sure, a modal can work without, but there are many advantages to using the Portal. – Brett DeWoody Jul 15 '22 at 08:28
  • Can you explain, in a nutshell, why exactly it's a must for a11y? What do you consider the main advantage, just to include 1 of the many advantages you mention. – inwerpsel Jul 15 '22 at 10:35
  • Not saying you're incorrect btw. My point is your answer could be improved by going into a bit more detail ;) – inwerpsel Jul 15 '22 at 10:37
  • Could it be [this accessibility requirement](https://assortment.io/posts/accessible-modal-component-react-portals-part-1#the-magic-of-react-portals) you're referring to? _When rendered, the Modal is appended to the end of document.body._ – inwerpsel Jul 15 '22 at 12:06
  • More or less that's the idea. There are a number of articles on best practices around modals and how they should be setup - namely that it should use a different top-level element, [as described here](http://web-accessibility.carnegiemuseums.org/code/dialogs/). – Brett DeWoody Jul 15 '22 at 16:02
11

I ran into another use case a few months ago. Because React 16 portals were not available, I had to use a home-baked portal implementation.

I was creating SVG graphs. All of the lines, paths, and so forth needed to be rendered inside an <svg> element. But I wanted to use HTML to render text labels (for a number of reasons). This meant that an object on the graph and its label would necessarily be in separate parts of the DOM. But with portals, I could still keep all of a graph component's logic together. Here's a fictitious example:

const LabeledPoint = ({ x, y, r, labelText }) => [
    <circle cx={x} cy={y} r={r} />,
    <GraphLabel x={x + 5} y={y}>{labelText}</GraphLabel>,
];

You would use this component inside an <svg> element. The GraphLabel component would use a portal to render labelText in an HTML div element at the same coordinates as that <svg>, using absolute positioning to place it at the correct coordinates.

This way, all of the logic for a single component could be in one place even if I needed to render the actual DOM elements in different places for technical reasons.

Thom Smith
  • 13,916
  • 6
  • 45
  • 91
  • thanks this is the best practical example I've come across thus far – Ghan Apr 30 '18 at 22:59
  • This example actually gets around a big challenge with portals. Each time a portal is rendered for the first time, the content is always inserted at the end of the target HTML element. E.g. an if statement at the start of your JSX starts showing a portal before portals that were already rendered, it won't be inserted before those. So you can't control the order of HTML in the target element and you need something like absolute positioning (x and y props) instead. – inwerpsel Jul 14 '22 at 08:52
0

Portals is very useful feature when you need to render your component outside the DOM hierarchy of the parent component.

You define a portal using the following syntax:

ReactDOM.createPortal(child, container) 

The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.

See the following tutorial to see how and why to use portals:

https://www.youtube.com/watch?v=lOMU9BeIrO4

SyndicatorBBB
  • 1,757
  • 2
  • 26
  • 44
0

Before going into my answer, I'll just note that I interpret the question as "in which cases is there no alternative to using a portal, or a portal would be a demonstrably much better solution?"

There are very few cases where portals are the only solution. A lot of the time there's a way to structure your app so that you don't need to use them. There's some niche use cases, but even there it's usually not the only solution.

For example in the SVG use case, you could instead create a Labels component that takes an SVG React element as argument, and then loops recursively over the children to construct an HTML element with matching labels in the right position. That would as a bonus also make the SVG code a lot simpler. If the SVG is user editable, you'd have to store its state as a whole anyway on each change, allowing you to easily pass the state back into both SVG and label elements. That said, here the portal solution seems at least on par with the alternatives, and could be the simplest in some circumstances.

Dispatching plugin components

Portals can be useful for library/framework authors. It allows plugins to render multiple components in the same element, each of which the framework then portals to a different position in the UI (e.g. editor area, sidebar).

WordPress's block editor uses this for a few things, they call it SlotFill. For example if you're coding a new block in a plugin. You always provide an edit component for each block, which is rendered to the WYSIWYG editor. If it includes an InspectorControls component, everything inside it will go into the sidebar.

The Block Toolbar works in the same way. Content of the <BlockControls/> element is moved to the toolbar that is displayed right above the block content.

This is implemented as a portal to a registered slot.

The advantage of a portal here is that it allows a block's code to reuse the state and hooks in all components, even though they are not rendered in the same place. This makes the process of adding sidebar code very easy and with minimal repetition. If plugins instead needed to provide each of these components as a standalone React component, managing their state would be much more complex.

Example

You won't find ReactDOM.createPortal in the example itself. It's rather an example of how a plugin can benefit from a framework that uses it. See WordPress's source code if you're interested in the implementation details.

I added a simple useState hook to the mentioned InspectorControls example and removed some irrelevant parts.

edit: ( { attributes, setAttributes } ) => {
    const [myColor, setMyColor] = useState('#fff');
      return (
        <div>
            <InspectorControls key="setting">
                // Simplified HTML, real world blocks use more complex HTML here.
                <ColorPalette
                    value={myColor}
                    onChange={ setMyColor}
                />
            </InspectorControls>
            <TextControl
                value={ attributes.message }
                onChange={ ( val ) => setAttributes( { message: val } ) }
                style={ {
                    backgroundColor: myColor,
                    color: attributes.text_color,
                } }
            />
        </div>
    );
},
inwerpsel
  • 2,677
  • 1
  • 14
  • 21