Jan 14, 2019

Understanding Portals in React


Introduction:

This article explains about Portals in React.

What is a Portal?

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. Typical use cases for Portals are Modals/Dialogs, pop-ups, tooltips, global message notifications etc.

Reference: React Documentation.

Why to use Portals?

A Portal can be anywhere in the DOM tree but behaves like a normal React child. So, all the powers of React component apply to Portals as well. Some of the examples are as follows:

  • Features like context(Please refer to my article on Context for more details) work exactly the same because portal still exists in the React tree regardless of its position in the DOM tree.
  • Event bubbling:  An event fired from inside a portal will propagate to Parent components in the containing React tree, even if these elements are not parents in the DOM tree.

How to use Portals?

Basic implementation of Portals:

import React from "react";
import { createPortal } from "react-dom";

const modalRoot = document.getElementById("modal");

class Modal extends React.Component {
  constructor(props) {
    super(props);

    this.el = document.createElement("div");
  }

  componentDidMount() {
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    //To avoid memory leaks we perform cleanup before Unmount
    modalRoot.removeChild(this.el);
  }

  render() {
    return createPortal(this.props.children, this.el);
  }
}

export default Modal;

Demystifying the above code snippet.

  • The Modal component is appended into modalRoot from the DOM tree.
  • createPortal takes all the children from the props and inserts them into this.el.
  • When the Modal component is called, All the children inside it are passed as props.children to the Modal component.
  • For example, let us consider the following snippet from App.js file:
Modal>
  <p>This would send an Email to AK. Are you sure?</p>
  <div className="buttons">
    <button onClick={this.toggleModal}>Yes</button>
    <button onClick={this.toggleModal}>No</button>
  </div>
</Modal>
  • Tags <p> and <div> are passed as props.children to the Modal component.
  • Content inside the Modal will be rendered into a different DOM node. However, in the React tree, it is present inside the React Parent Component (App).
  • DOM tree from Browser Dev Tools:

DOM structure from Chrome Dev tools

  • React tree from BrowserReact Dev tools
    React component structure from React tree

Event bubbling with Portals:

Render block from App.js:

render() {
  return (
    <div className="App" onClick={this.handlePageClick}>
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>AK at BeautifulCode is good at React!</p>
        <p>
          {" "}
          To ask him questions please{" "}
          <button onClick={this.toggleModal}>Cick here!</button>
        </p>
        <p>Total number of Page Clicks so far {this.state.pageClicks} </p>
      </header>
      {this.state.showModal && (
        <Modal>
          <p>This would send an Email to AK. Are you sure?</p>
          <div className="buttons">
            <button onClick={this.toggleModal}>Yes</button>
            <button onClick={this.toggleModal}>No</button>
          </div>
        </Modal>
      )}
    </div>
  );
}

Observations from above snippet:

  • On Click, events are present at both the Modal buttons and a div outside the Modal.
  • Whenever a button inside the Modal is clicked, an event gets propagated to Parent Div. This phenomenon of propagating events to ancestral elements is known as Event Bubbling

Source Code:

Please find the source code at https://github.com/abhilash4252/React-Portals-Demo/

When to use?

  • When a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. Examples: dialogs, hover cards, and tool-tips.

Abhilash Reddy Kotte

Frontend Engineering