How to use ReactDOM.createPortal() in React 16?

React v16 has just released a couple of hours ago (Yay!!) which officially supports Portal.

What is Portal? Since How long has it been there?

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

Portal is not new concept in the react community. Many libraries are available that supports this kind of functionality. e.g react-portal and react-gateway.

What happens when rendering any react app?

Generally, when rendering any React application, a single DOM element is used to render the whole React tree.

class HelloReact extends React.Component {
   render() {
       return (
          <h1>Hello React</h1>
       );
   }
}

ReactDOM.render(<HelloReact />, document.getElementById('root'));

As you can see we are rendering our react component into a DOM element having id root.

What is Portal and why is it needed? Why is it there?

Portals are a way to render React children outside the main DOM hierarchy of the parent component without losing the react context. I'm emphasizing on it because very popular libraries like react-router, redux heavily uses the react context. So context availability when using Portal is very helpful.

As per the react docs,

A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually "break out" of its container. For example, dialogs, hovercards, and tooltip.

So, with portals, you can render a parallel react tree onto another DOM node when needed. Even though it is rendered in the different DOM node, parent component can catch the uncaught events. See this codepen provided in the docs itself.

The below example should give your more idea:

// index.html
<html>
    <body>
        <div id="root"></div>
        <div id="another-root"></div>
    </body>
</html>

// index.jsx
const mainContainer = document.getElementById('root');
const portalContainer = document.getElementById('another-root');

class HelloFromPortal extends React.Component {
    render() {
         return (
           <h1>I am rendered through a Portal.</h1>
         );
    }
}

class HelloReact extends React.Component {
    render() {
        return (
             <div>
                 <h1>Hello World</h1>
                 { ReactDOM.createPortal(<HelloFromPortal />, portalContainer) }
             </div>
        );
    }
}

ReactDOM.render(<HelloReact />, mainContainer);

https://codesandbox.io/s/62rvxkonnw

You can use devtools inspect element and see that <h1>I am rendered through a Portal.</h1> is rendered inside #another-root tag, whereas <h1>Hello World</h1> is rendered inside #root tag.

Hope this helps :)

Update: To answer @PhillipMunin's comment.

What is the different between ReactDOM.render and ReactDOM.createPortal?

  1. Even though the component rendered through the portal is rendered somewhere else (Outside the current container root), it remains present as the child of the same parent component. (Who invoked the ReactDOM.createPortal) So any events on the child are propagated to the parent. (Ofc, This doesn't work if you manually stop the propagation of the event.)

  2. Same context is accessible inside the component rendered through a portal. But not in the case when we do ReactDOM.render directly.

I've created another demo to illustrate my point. https://codesandbox.io/s/42x771ykwx


I think we can achieve same feature by creating an external dom node outside 'root' element

node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal {...props} />,node)

Let's say we are creating a modal view, we can create a wrapper for the same

import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import Modal from './Modal'
let node = null
const ModalWrapper = (props) =>{
    useEffect(()=>{
        node && ReactDOM.render(<Modal {...props} />,node)
    })
    useEffect(()=>{
        node = document.createElement('div')
        document.body.appendChild(node)
        ReactDOM.render(<Modal {...props} />,node)
    },[])
    return(
        <script />
    )
}

Here is an example Create Modal with React Portal and React Hooks from scratch without external library

React-modal with portal