21
Learn React Portal and Its Applications
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.
The syntax of React Portal is:
ReactDOM.createPortal(child, container)
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.html
file and type:
<div id="root"></div> <!-- this is root node -->
<div id="portal-root"></div> <!-- this node portal node -->
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);
}
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>
);
}
}
In this example, the App.js
file 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 div
element.
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.
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 div
above rootdiv
.
<div id=”modal”></div>
<div id=”root”></div>
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;
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>
);
}
}
In this code, we’ve set the showModal
property to false. The Modal will be visible at the click of the button.
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>
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");
}
}
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);
}
}
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>
);
}
A click on the button will trigger an event that will bubble up to the parent as we have not defined any onClick
attribute.
function Child () {
return (
<div className="modal">
<button>Click</button>
</div>
);
}
ReactDOM.render(<Parent />, appRoot);
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.
21