What is React Portals.

Sherin Shaju Athickal
3 min readApr 26, 2020

I think portals in React are familiar to most people, even if they are rarely used. Here is how they are described in the React documentation:

“Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.”

ReactDOM.createPortal(child, container)

The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.

Usually, our entire React app is rendered in just one DOM node in the HTML. But with portals, we can define additional nodes where we can mount parts of our app, such as individual components.

Within the official documentation, however, portals are described in a rather cumbersome and complicated manner, which is why react-portal provides a solution.

And that’s how we do it:

import { Portal } from ‘react-portal’<Portal node={document && document.getElementById(‘portal’)}>
<p>This is portaled into the portal div!</p>
</Portal>

Just target your portal container in your HTML code with the typical JavaScript selectors, like getElementById, and you are good to go.

In the public/index.html file of your React.js application:

<div id=”root”></div><div id=”portal”></div>

As you can see, the root-element every React app needs is there as usual, and the portals you can create with this cool library can of course be outside of the root.

Further tip: You can toggle your portal because it is just a React component in the end:

{this.state.show ? (
<Portal node={document && document.getElementById(‘portal’)}>
<p>Portal content</p>
</Portal>
) : null}

Usage

Normally, when you return an element from a component’s render method, it’s mounted into the DOM as a child of the nearest parent node:

render() {
// React mounts a new div and renders the children into it
return (
<div> {this.props.children}
</div> );
}

However, sometimes it’s useful to insert a child into a different location in the DOM:

render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode );
}

A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. For example, dialogs, hovercards, and tooltips.

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. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.

This includes event bubbling. An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree. Assuming the following HTML structure:

<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>

A Parent component in #app-root would be able to catch an uncaught, bubbling event from the sibling node #modal-root.

// These two containers are siblings 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');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal( this.props.children, this.el ); }
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() { this.setState(state => ({ clicks: state.clicks + 1 })); }
render() {
return (
<div onClick={this.handleClick}> <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() {because there is no 'onClick' attribute defined return (
<div className="modal">
<button>Click</button> </div>
);
}
ReactDOM.render(<Parent />, appRoot);

Try it on CodePen

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.

Happy hacking 👨‍💻

--

--