Using React Portals to Render Children Outside the DOM Hierarchy
Publikováno: 15.1.2019
Say we need to render a child element into a React application. Easy right? That child is mounted to the nearest DOM element and rendered inside of it as a result.
render() {
return (
<div>
// Child to render inside of the div
</div>
);
}
But! What if we want to render that child outside of the div somewhere else? That could be tricky because it breaks the convention that a component needs to render as a new … Read article
The post Using React Portals to Render Children Outside the DOM Hierarchy appeared first on CSS-Tricks.
Say we need to render a child element into a React application. Easy right? That child is mounted to the nearest DOM element and rendered inside of it as a result.
render() {
return (
<div>
// Child to render inside of the div
</div>
);
}
But! What if we want to render that child outside of the div somewhere else? That could be tricky because it breaks the convention that a component needs to render as a new element and follow a parent-child hierarchy. The parent wants to go where its child goes.
That’s where React Portals come in. They provide a way to render elements outside the DOM hierarchy so that elements are a little more portable. It may not be a perfect analogy, but Portals are sort of like the pipes in Mario Bros. that transport you from the normal flow of the game and into a different region.
The cool thing about Portals? Even though they trigger their own events that are independent of the child’s parent element, the parent is still listening to those events, which can be useful for passing events across an app.
We’re going to create a Portal together in this post then make it into a re-usable component. Let’s go!
The example we’re building
Here’s a relatively simple example of a Portal in action:
See the Pen React Portal by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.
Toggling an element’s visibility is nothing new. But, if you look at the code carefully, you’ll notice that the outputted element is controlled by the button even though it is not a direct descendent of it. In fact, if you compare the source code to the rendered output in DevTools, you’ll see the relationship:
So the outputted element’s parent actually listens for the button click event and allows the child to be inserted even though it and the button are separate siblings in the DOM. Let’s break down the steps for creating this toggled Portal element to see how it all works.
Step 1: Create the Portal element
The first line of a React application will tell you that an App element is rendered on the document root using ReactDOM. Like this;
ReactDOM.render(<App />, document.getElementById("root"));
We need to place the App element in an HTML file to execute it:
<div id="App"></div>
Same sort of thing with Portals. First thing to creating a Portal is to create a new div element in the HTML file.
<div id="portal"></div>
This div will serve as our target. We’re using #portal
as the ID, but it doesn’t have to be that. Any component that gets rendered inside this target div will maintain React’s context. We need to store the div as the value of a variable so we can make use of the Portal component that we’ll create:
const portalRoot = document.getElementById("portal");
Looks a lot like the method to execute the App element, right?
Step 2: Create a Portal component
Next, let’s set up the Portal as a component:
class Portal extends React.Component {
constructor() {
super();
// 1: Create a new div that wraps the component
this.el = document.createElement("div");
}
// 2: Append the element to the DOM when it mounts
componentDidMount = () => {
portalRoot.appendChild(this.el);
};
// 3: Remove the element when it unmounts
componentWillUnmount = () => {
portalRoot.removeChild(this.el);
};
render() {
// 4: Render the element's children in a Portal
const { children } = this.props;
return ReactDOM.createPortal(children, this.el);
}
}
Let’s step back and take a look at what is happening here.
We create a new div element in the constructor and set it as a value to this.el
. When the Portal component mounts, this.el
is appended as a child to that div in the HTML file where we added it. That’s the <div id="portal"></div>
line in our case.
The DOM tree will look like this.
<div> // Portal, which is also portalRoot
<div> // this.el
</div>
</div>
If you’re new to React and are confused by the concept of mounting and unmounting an element, Jake Trent has a good explanation. TL;DR: Mounting is the moment the element is inserted into the DOM.
When the component unmounts we want to remove the child to avoid any memory leakage. We will import this Portal component into another component where it gets used, which is the the div that contains the header and button in our example. In doing so, we’ll pass the children elements of the Portal component along with it. This is why we have this.props.children
.
Step 3: Using the Portal
To render the Portal component’s children, we make use of ReactDOM.createPortal()
. This is a special ReactDOM method that accepts the children and the element we created. To see how the Portal works, let’s make use of it in our App component.
But, before we do that, let’s cover the basics of how we want the App to function. When the App loads, we want to display a text and a button — we can then toggle the button to either show or hide the Portal component.
class App extends React.Component {
// The initial toggle state is false so the Portal element is out of view
state = {
on: false
};
toggle = () => {
// Create a new "on" state to mount the Portal component via the button
this.setState({
on: !this.state.on
});
};
// Now, let's render the components
render() {
const { on } = this.state;
return (
// The div where that uses the Portal component child
<div>
<header>
<h1>Welcome to React</h1>
</header>
<React.Fragment>
// The button that toggles the Portal component state
// The Portal parent is listening for the event
<button onClick={this.toggle}>Toggle Portal</button>
// Mount or unmount the Portal on button click
<Portal>
{
on ?
<h1>This is a portal!</h1>
: null
}
</Portal>
</React.Fragment>
</div>
);
}
}
Since we want to toggle the Portal on and off, we need to make use of component state to manage the toggling. That’s basically a method to set a state of on
to either true
or false
on the click event. The portal gets rendered when on
is true; else we render nothing.
This is how the DOM looks like when the on
state is set to true
.
When on
is false
, the Portal component is not being rendered in the root, so the DOM looks like this.
More use cases
Modals are a perfect candidate for Portals. In fact, the React docs use it as the primary example for how Portals work:
See the Pen Example: Portals by Dan Abramov (@gaearon) on CodePen.
It’s the same concept, where a Portal component is created and a state is used to append the its child elements to the Modal component.
We can even insert data from an outside source into a modal. In this example, the App component lists users fetched from an API using axios.
See the Pen React Portal 3 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.
How about tooltips? David Gilberston has a nice demo:
See the Pen React Portal Tooptip by David Gilbertson (@davidgilbertson) on CodePen.
J Scott Smith shows how Portals can be used to escape positioning:
He has another slick example that demonstrates inserting elements and managing state:
Summary
That’s a wrap! Hopefully this gives you a solid base understanding of Portals as far as what they are, what they do, and how to use them in a React application. The concept may seem trivial, but having the ability to move elements outside of the DOM hierarchy is a handy way to make components a little more extensible and re-usable… all of which points to the core benefits of using React in the first place.
More information
- React Portals Documentation
- React v16 Release Notes - The release where Portals were introduced
- CodePen Examples - A search of React Portals
The post Using React Portals to Render Children Outside the DOM Hierarchy appeared first on CSS-Tricks.