React 16 - Render a child component into another DOM node using Portals

React 16 is here, and it comes with massive changes. Everything under the hood got rewritten and in the process they added a lot of new cool features.

In the previous post, we looked at Error Boundaries and how they'll help you to control errors during rendering.

In this post we'll take a closer look at another new feature called Portals.

Rendering child components in an outer DOM node

By default react components renders into the DOM as children of their parents. This has been the way to get things done - until now.

Portals introduce a new way of rendering into the DOM. It gives you the possibility of rendering a child component into a DOM node outside the current DOM hierarchy.

Creating a Portal

Creating a portal is simple. All you need is to provide the component you want to render and the DOM element you want to mount the component into, then you simply use the new createPortal method.

ReactDOM.createPortal(componentToRender, elementToRenderInto);  

That's it! You now know how to create portals.

Using it in an application

As you saw creating a portal is quite simple, but to fully grasp the concept, let's use it in a simple react application to see how it behaves.

Say we got a simple application that in addition to its normal functionality should be able to add notifications to a div other than where the react application is mounted.

To illustrate this, let's add two divs in the html - one meant for showing notifications to the user and one for mounting our react application.

<body>  
  <div id="notification-box"></div>
  <div id="content"></div>
</body>  

Next, we mount our react application as normal to the div with id content.

const App = () => (  
  <div>
    <NotificationBox>
      <Notification/>
    </NotificationBox>
    <Content/>
  </div>
);

ReactDOM.render(<App/>, document.getElementById('content'));  

Our application is quite simple. It contains a Content component, and a NotificationBox component that contains a Notification component which is the notification we want to show to the user.

All of this is currently rendered in the content div, which is not what we want for the notifications. So to be able to render notifications into notification-box, we need to take usage of the portal.

Jumping into the NotificationBox component, we'll see how this is done.

const NotificationBox = ({children}) => {  
  return ReactDOM.createPortal(
    children,
    document.getElementById('notification-box')
  );
};

Great! Now NotificiationBox will render it's children into notification-box as wanted.

Still acting like a child in the application

The coolest thing about portals, is the fact that even if a child component may be mounted to another place in the DOM, it's still a full worthy child component in the react application.

We can see this in a few ways.

Events of child components still propagate to the parent component

One way to see the parent/child relationship between two react components using portals is that events still propagate up the component hierarchy.

We can see this by adding an onClick attribute to the App component, then send a component with a button through the portal.

const NotificationWithButton = () => (  
  <button>Click</button>
);

class App extends React.Component {

  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log("click")
  }

  render() {
    return (
      <div  onClick={this.handleClick}>
        <NotificationBox>
          <NotificationWithButton/>
        </NotificationBox>
        <Content/>
      </div>
    )
  }
}

If we now click on the button rendered in the notification div, the event still is caught by App and will trigger the handleClick method.

Errors of child components still propagate to the parent component

Now, what happens if any error occurs in the child component during rendering?

We'll see the same natural propagation of the errors as with the events.

To illustrate this, let's add an error boundary in the App component that is wrapping the NotificationBox component.

const App = () => (  
  <div>
    <ErrorBoundary>
      <NotificationBox>
        <Notification/>
      </NotificationBox>
    </ErrorBoundary>
    <Content/>
  </div>
);

With the error boundary in place, we'll catch errors even if they occur in the component rendered in the Portal. This way we can handle things where it feels natural from the application's point of view.

Thats it! As you can see, using portals, we can easily mount child components to DOM nodes outside their parents, yet keeping the full worthy relationship with their parent.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe