Learn React Portal and Its Applications

Bernard Bado - Nov 23 '21 - - Dev Community

React Portal is a great way to render floating components. React Portal renders child components into a DOM node without disturbing event propagation and provides greater flexibility outside of the hierarchy of a parent container.

React Portals have made it easier to create floating modals. It provides an escape from monotonous hierarchy with the help of Portals. But at the same time, React Portals do not disturb event propagation. The parent component is listening to those events; thus, making it more feasible to handle the events across an application.

Let’s look at the creation of React Portal in this article.

React Portal — Example

The syntax of React Portal is:

ReactDOM.createPortal(child, container)
Enter fullscreen mode Exit fullscreen mode

In this syntax, the first argument is the React child component. It could be a string or a fragment, or an element. The second argument is the DOM element.

To create a new DOM node, navigate to the public/index.htmlfile and type:

<div id="root"></div> <!-- this is root node -->
<div id="portal-root"></div> <!-- this node portal node -->
Enter fullscreen mode Exit fullscreen mode

Now, to implement React Portal, create a js file. Let’s name it Modal.js. In this file, write the code to create a modal.

import ReactDOM from "react-dom";

export default function Modal() {
  const portalDOM = document.getElementById("portal-root");
  return ReactDOM.createPortal(<div>Modal Portal</div>, portalDOM);
}
Enter fullscreen mode Exit fullscreen mode

Now, add the modal in the App.js file of the React application.

import React from "react";
import "./App.css";
import Modal from "./components/modal";

class App extends React.Component {
  render() {
    return (
      <div>
        <Modal />
      </div>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

In this example, the App.jsfile renders the modal that you created. If you inspect the code using the browser dev tool, you will see that it is evident that the modal has its divelement.

Common Use Cases of React Portals

The most common use cases for React Portals are tooltips, dialog boxes, hover cards, modals, or loaders. You can also use React Portals when the child component should be visually separate from the parent container. It is even more useful when a parent component has overflow: hidden or z-index styling settings.

Whatever the use case may be, there are some points that you must keep in mind while working with React Portals:

  • Events will be propagating to the React tree ancestors.
  • React has control over the child component’s lifecycle even when using the Portals.
  • Portals affect the HTML DOM structure and not the React tree.
  • You need to define an HTML DOM element as a mount point for the Portal’s component.

How to Use React Portal in React — A Modal Example

One of the most common use cases is Modals in React. Lots of applications use Modals because it improves the overall UX of the application. Furthermore, they are useful in grabbing the user’s attention. React official documentation also uses Modals as an example to demonstrate how the models work perfectly.

Before you start with the modal implementation, make sure that React is updated to the latest version.

Like the sample example shown above, add the modal divabove rootdiv.

<div id=”modal”></div>
<div id=”root”></div>
Enter fullscreen mode Exit fullscreen mode

Now, create a Modal component and name it Modal.js. This component will act like a modal, and you can put content inside it.

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

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

class Modal extends React.Component {
  constructor(props) {
    super(props);
    // Create div for this modal
    this.element = document.createElement("div");
  }
  // Append div to the div#modal
  componentDidMount() {
    modalRoot.appendChild(this.element);
  }
  /**
   * Make sure to remove the div when it is closed, and save the memory.
   */

  componentWillUnmount() {
    modalRoot.removeChild(this.element);
  }
  render() {
    return createPortal(this.props.children, this.element);
  }
}
export default Modal;

Enter fullscreen mode Exit fullscreen mode

Add CSS to your file as per your requirements to set the layout. In the next step, add import this Modal into any of your components.

import React from "react";
import Modal from "./Modal";

export default class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showModal: false,
    };
  }
  toggleModal = () => {
    this.setState({
      showModal: !this.state.showModal,
    });
  };
  render() {
    const { showModal } = this.state;
    return (
      <React.Fragment>
        <button className="modal-toggle-button" onClick={this.toggleModal}>
          {!showModal ? "Open Modal" : "Close Modal"}
        </button>
        {showModal ? (
          <Modal>
            <h1>Sample Heading</h1>
            <p>Sample Paragraph</p>
            <button className="modal-close" onClick={this.toggleModal}>
              X
            </button>
          </Modal>
        ) : null}
      </React.Fragment>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we’ve set the showModalproperty to false. The Modal will be visible at the click of the button.

Event Bubbling in React Portals

Although you can create a React Portal anywhere in the document, it is still under the React tree, regardless of its position in the DOM hierarchy. The same happens when the child component propagates the event. The event fired from the child propagates to its ancestors in the React tree, even if they do not exist in the DOM hierarchy.

Let’s look at an example of event bubbling.

For instance, you have this HTML structure:

<html>
<body>
<div id=”app-root”></div>
<div id=”modal-root”></div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

A parent component in #app-root will be able to catch a bubbling event from its sibling node #modal-root as both are sibling containers in the DOM.

const appRoot = document.getElementById("app-root");
const modalRoot = document.getElementById("modal-root");

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement("div");
  }
}
Enter fullscreen mode Exit fullscreen mode

The Modal’s children are mounted on a detached DOM node. If there is a need to attach a child component to the DOM tree, add the state to the Modal and only render the child component.

import React from "react";

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { clicks: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  componentDidMount() {
    modalRoot.appendChild(this.el);
  }
  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }
  render() {
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}
Enter fullscreen mode Exit fullscreen mode

The event handleClick() will fire when you click the button in Child. Keep in mind that the button is not the immediate descendant in the DOM hierarchy.

handleClick() {
    this.setState(state => ({
      clicks: state.clicks + 1
    }));
}

render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
        <Modal>
           <Child />
        </Modal>
      </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

A click on the button will trigger an event that will bubble up to the parent as we have not defined any onClickattribute.

function Child () {
    return (
      <div className="modal">
       <button>Click</button>
      </div>
    );
}

ReactDOM.render(<Parent />, appRoot);
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, you learned about React Portal in React Application, its use case, and event bubbling. The concept is easy; however, it provides the flexibility to handle reusable components outside the DOM hierarchy without breaking the default event behavior.

. . . . . . . . . . . . . . . . . . . . . . . . . . .