Portals in React

Introduction

React is a component-based language, meaning we can have different components coming together to display a webpage. 

This component-based architecture allows us to reuse our code in various components with the help of render props in react.

For a brief understanding, Portals provide a first-class way to render children into a DOM node outside the parent component’s DOM hierarchy.

Further, we will be discussing one interesting topic in react, i.e., portals in react. 

Now let us get into the concept of portals in react.

What are portals in react?

Portals in react provide a way to render children into a DOM node that exists outside the hierarchy of the parent component.

The react version 16.0 introduced the portals in react.

Basically, We have had one DOM element in our HTML on which we were mounting our react app. 

Looking at the index.html file, we can see that element is the element with id equal to “root.” 

index.html

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="utf-8" />

    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <meta name="theme-color" content="#000000" />

    <meta

      name="description"

      content="Web site created using create-react-app"

    />

    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    <title>React App</title>

  </head>

  <body>

    <noscript>You need to enable JavaScript to run this app.</noscript>

    <div id="root"></div>

  </body>

</html>

 

 

In the index.js file, we use reactDOM.render and mount our app component onto the root element.

index.js

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

import App from './App';

import reportWebVitals from './reportWebVitals';

ReactDOM.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>,

  document.getElementById('root')

);

reportWebVitals();

 

 

In the browser, in the element DOM tree, we can see that every single component in our react application falls under the root element, the div element with id equals to root.

 

 

The react portals allow us to break out of this DOM tree; we can render a component in the DOM node that is not under this root element.

How to implement portals in react?

Firstly we will add a DOM node that is outside of the root element.

In index.html, right below the root element, we add a new div tag with id equals portal-root.

Index.html

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="utf-8" />

    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <meta name="theme-color" content="#000000" />

    <meta

      name="description"

      content="Web site created using create-react-app"

    />

    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    <title>React App</title>

  </head>

  <body>

    <noscript>You need to enable JavaScript to run this app.</noscript>

    <div id="root"></div>

    <div id="portal-root"></div>

  </body>

</html>

 

 

Secondly, We will create a new Component, namely, PortalDemo.js; within the file, we will create a functional component.

In the JSX, we will add a simple heading tag.

PortalDemo.js

import React from "react";

 

function PortalDemo() {

  return <h1>Portals in react</h1>;

}

export default PortalDemo;

 

Then, we will include the component in the app component. 

App.js

import React from "react";

import "./App.css";

import PortalDemo from "./components/PortalDemo";

function App() {

  return (

    <div classname="App">

      <PortalDemo />

    </div>

  );

}

export default App;

 

 

After saving all the files and having a look at the browser looking in the DOM tree, we can see that on inspecting the heading, we see that the heading element falls under the root element and not under the “portal-root” element.

 

 

Let us change that,

We will use ReactDOM.createPortal method to insert this component under the portal-root node.

So in the PortalDemo component at the top, we need to import the ReactDOM package,

Then in the render method, instead of simply returning the JSX, we will return ReactDOM.createPortal. 

This method takes two arguments; the first one is the JSX you want to render; over here, it is the heading, and the second parameter is the DOM node on which we want to mount the component.

PortalDemo.js

import React from "react";

import ReactDOM from "react-dom";

function PortalDemo() {

  return ReactDOM.createPortal(

    <h1>Portals in react</h1>,

    document.getElementById("portal-root")

  );

}

export default PortalDemo;

 

 After saving all the files, we still see the heading “Portals in react.” 

When we inspect the element, we see that the heading tag is inside the portal-root DOM node.

So in the react application, even though all the components are children to the app component, and the app component is mounted on the root DOM node, it is possible to break away from that and to mount on any DOM node that you wish to use by the help of React portals.

Note: The first parameter to create a portal can be any element that react can render. It can be numbers, strings, JSX, or even components.

Why do we need portals?

One of the use cases, which is brought up, is having to deal with the parent component CSS when the child component is a model, pop-up, or tooltip.

Example

First, we will see how the code works with the portals.

In the index.html, we have two div tags, one with id “root” and the other “modal-root.”

In index.js, we have references to both elements. We have a Modal component.

Index.js

import React from 'react'

import ReactDOM from 'react-dom'

 

const root = document.getElementById('root')

const modalRoot = document.getElementById('modal-root')

 

class Modal extends React.Component {

 render() {

   return ReactDOM.createPortal(

     <div

       style={{

         position: 'absolute',

         top: '0',

         bottom: '0',

         left: '0',

         right: '0',

         display: 'grid',

         justifyContent: 'center',

         alignItems: 'center',

         backgroundColor: 'rgba(0,0,0,0.2)',

       }}

       onClick={this.props.onClose}

     >

       <div

         style={{

           padding: 20,

           background: '#fff',

           borderRadius: '2px',

           display: 'inline-block',

           minHeight: '300px',

           margin: '1rem',

           position: 'relative',

           minWidth: '300px',

           boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',

           justifySelf: 'center',

         }}

       >

         {this.props.children}

         <hr />

         <button onClick={this.props.onClose}>Close</button>

       </div>

     </div>,

     modalRoot,

   )

 }

}

 

class App extends React.Component {

 state = {showModal: false}

 handleShowMessageClick = () => this.setState({showModal: true})

 handleCloseModal = () => this.setState({showModal: false})

 render() {

   return (

     <div

       style={{

         height: '100%',

         display: 'grid',

         justifyContent: 'center',

         alignItems: 'center',

       }}

     >

       <div

         style={{

           maxWidth: 400,

           position: 'relative',

         }}

       >

         <h1>My App</h1>

         <p>

           This is an example of how you might use React.createPortal. I think

           it is a pretty neat API that is yet another awesome escape hatch

           that React provides for practical reasons. Sometimes you just need

           to render something completely outside the React Tree.

         </p>

         <button onClick={this.handleShowMessageClick}>

           Show Secret Modal

         </button>

         {this.state.showModal ? (

           <Modal onClose={this.handleCloseModal}>

             This is the secret modal message!

           </Modal>

         ) : null}

       </div>

     </div>

   )

 }

}

 

ReactDOM.render(<App />, root)

 

 

Output:
 

Now, we remove the react portal from the Modal component from the index.js file by removing the method ReactDOM.createPortal and its second parameter. Modifying it to as shown below:

class Modal extends React.Component {

 render() {

   return (

     <div

       style={{

         position: 'absolute',

         top: '0',

         bottom: '0',

         left: '0',

         right: '0',

         display: 'grid',

         justifyContent: 'center',

         alignItems: 'center',

         backgroundColor: 'rgba(0,0,0,0.2)',

       }}

       onClick={this.props.onClose}

     >

       <div

         style={{

           padding: 20,

           background: '#fff',

           borderRadius: '2px',

           display: 'inline-block',

           minHeight: '300px',

           margin: '1rem',

           position: 'relative',

           minWidth: '300px',

           boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',

           justifySelf: 'center',

         }}

       >

         {this.props.children}

         <hr />

         <button onClick={this.props.onClose}>Close</button>

       </div>

     </div>

   )

 }

}

 

 

The output after that will be as shown below.

Output:

We can see that without portals, we can see that the output is distorted, and the model does not work the way we want it.

It is because the CSS of the parent is getting used by the model, which is not desirable. We want to have different styling for the model component that is achieved by the portals in react. 

Jethalal Jethalal Shocked GIF - Jethalal Jethalal Shocked Shocked Face -  Discover &amp; Share GIFs

 

Event Bubbling through portals.

Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. 

An event fired from inside the portal will propagate to the ancestor into containing react tree, even if those elements are not the ancestors in the DOM tree. This phenomenon is known as event bubbling

Example

Assume the following structure

<div id="app-root"></div>

<div id="modal-root"></div>

 

 

A Parent component in the div with id as app-root would catch an uncaught, bubbling event from the sibling node in the div with id as modal-root.

Index.js

const appRoot = document.getElementById('app-root');

const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {

  constructor(props) {

    super(props);

    this.ele = document.createElement('div');

  }

  render() {

    return ReactDOM.createPortal(

      this.props.children,

      this.ele,

    );

  }

}

class Parent extends React.Component {

  constructor(props) {

    super(props);

    this.state = {clicks0};

    this.manageClick = this.manageClick.bind(this);

  }

  manageClick() {

    // This will fire when the button in Child is clicked,

    // updating Parent's state, even though button

    // is not a direct descendant in the DOM.

    this.setState(prevState => ({

      clicksprevState.clicks + 1

    }));

  }

 

  render() {

    return (

      <div onClick={this.manageClick}>

        <p>Number of clicks: {this.state.clicks}</p>

        <p>

          Open up the browser DevTools

          to observe that the button

          is not a child of the div

          with the onClick handler.

        </p>

        <Modal>

          <Child />

        </Modal>

      </div>

    );

  }

}

function Child() {

  // The click event on this button will bubble up to the parent,

  // because there is no 'onClick' attribute defined

  return (

    <div className="modal">

      <button>Click</button>

    </div>

  );

}

ReactDOM.render(<Parent />, appRoot);


Try it yourself on Codepen.

The click event is passed through the child component, which is propagated to the parent component despite being in a different DOM tree. However, they are in the same React tree.

Catching an event bubbling up from a portal in a parent component allows the development of more flexible abstractions that are not inherently reliant on portals. For example, if you render a <Modal /> component, the parent can capture its events regardless of whether it’s implemented using portals.

Frequently Asked Questions (FAQs)

  1. What are portals?
    Portals in react provide a way to render children into a DOM node that exists outside the hierarchy of the parent component.
     
  2. What is the need for portals?
    Portals in React allow us to shift a react component to another DOM node. It is also helpful in handling the styling of hovercards or pop-ups as a children component by moving them away from the parent DOM node.
     
  3. What is event bubbling?
    The phenomenon of an event fired from inside the portal will propagate to the ancestor into the same react tree, even if those elements are not the ancestors in the DOM tree.

Key Takeaways

We learned about the concept of portals in react. This is an advanced concept of portals in React that allows us to shift a react component to another DOM node. This article also explained why there is a need for portals in react specifically. We also learned about the implementation of portals in react.

Apart from this, you can also expand your knowledge by referring to these articles on Javascript and React.

For more information about the react framework for frontend development, get into the entire Frontend web development course.

 

Was this article helpful ?
0 upvotes

Comments

No comments yet

Be the first to share what you think