Portals receive first-class official support in React
With the recent release of React v16, there are a number of exciting new features. One of these new features is called
Portals have been a concept in the React community for quite some time1 and have gained first-class official support. The React docs define a
Portal as > Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
To best illustrate why this might be useful and necessary, let’s take a look at two examples: – Modals – Legacy Applications
A modal is a UI element that overlays on top of the main window of an application.
Let’s build an application with a simple modal2 to illustrate how this works:
To start, create a new application using
create-react-app. If you have never used
create-react-app before, run this command to install:
and the corresponding styles in a new file called
App.js with a basic button to toggle the
<Modal /> component:
and add the following css to
Now viewing the page in the browser, it should look like this:
Clicking on the
Show Modal button displays our
<Modal /> component:
Awesome! The modal renders successfully as expected! The sidebar takes up a big chunk of the screen, wouldn’t it be nice to have the sidebar slide in and out? Let’s go ahead and make that change:
We added a new state property called
which will toggle the sidebar open and closed. To make this work, we will have to update our
file to use the translate transform to slide the div in and out of the screen:
Now if we load up the page, we’ll see the new yellow button called
. Clicking the toggle button should now do what we expect, animating the sidebar left off the screen and with another click, moving the sidebar back to it’s original location.
Excellent, now with the sidebar open, let’s click
Show Modal to ensure everything is still working correctly.
Oh no! Our
<Modal /> component is now broken. How did this happen?
Let’s dig in and figure out how this happened! We used
position:fixed to position our modal relative to the viewport of the browser. This solution works but it has a major flaw.
position:fixed works, but with an important caveat.
Here is the documentation from MDN on the position fixed property:
fixed > The element is removed from the normal document flow; no space is created for the element in the page layout. Instead, it is positioned relative to the screen’s viewport and doesn’t move when scrolled. Its final position is determined by the values of top, right, bottom, and left. This value always creates a new stacking context. When an ancestor has the transform or perspective property set to something other than none, that ancestor is used as the container instead of the viewport (see CSS Transforms Spec). In printed documents, the element is placed in the same position on every page.
Looking at our HTML, we see that our modal is nested inside of our sidebar container:
property to the sidebar changed our fixed position from the viewport to the sidebar container! This means the styling of the
component can be broken by any styles set on the parent, which puts a damper on the re-usability of the component. Should we just remove the transform in the sidebar? That would solve the issue but makes the
component brittle to work with. We really want to maximize re-use and want the
component to work completely isolated from any styles set on any parent components.
What if there was a way to always render our component from the same location, say inside the
<body> of our HTML? We could then consistently style the component without worrying about the parent components styles.
How can we do this in our React application? Aren’t all components encapsulated under a root component when
ReactDOM.render is called?
This is the problem
Portals aim to solve. To see a
Portal in action, let’s first add a new div inside of
root-modal right before the closing
Next, let’s create a new file called
expects two arguments, the first being the Component you want rendered (in this case our
component) and the second being a DOM element (
in our example). In
we manually append the element into the DOM and on
, the element is removed from the DOM.
App.js, let’s add code to display our new
<Portal /> component alongside the
<Modal /> component:
and the corresponding css in
There should be a new button called
Show Modal, the application should still appear broken, as
transform is still set on the sidebar.
CLOSE, click on
<Modal /> component is back to normal. To see why, let’s examine the HTML:
Notice how the
is rendered completely outside our
component. This means that regardless of the styling of our
component, it will not break the
The other thing to note is that no changes were made to our original
<Modal /> component to get it working. The Portal takes
props like any other component.
Let’s say you have inherited a legacy application that is looking to add some new features and your team has decided to utilize React to build out those features. The components were built by your teammates and provided to you. Your job is to integrate them into the existing application.
The two components are:
- An interactive
<Profile> component that will display in the header.
- A customer support
<Chat> component that opens a window in the bottom right corner of the viewport.
<Profile> component will need to render to the top header and the
<Chat> component will need to render to a
<div> right before the closing
</body>, since we learned that
position:fixed cannot be trusted. Both components require certain user data to be passed into the component to work. This data is returned from an API endpoint called
/user/<userId>. This means there is some shared state between the components.
One approach might be to load the user information on the server and write it to the HTML, say
window.__USER_INFORMATION__, and then when the client loads up the page, read the variable
window.__USER_INFORMATION__ and pass that into each component when calling
Great, job done! But wait, you run the code and find out that the endpoint is quite slow. Due to these performance issues, you are now asked to make the request to the endpoint on page load instead of writing the data out to a global variable. We do not want to “over-fetch” data, so the user endpoint should only be called once. How can we render two components to different areas of the DOM, but continue to share some state between each component?
This is another use case where
Portals can help us out
Just as before, we can create a
Portal component for each component as we did before.
and create a new component to render to the DOM:
Then to mount the application:
Instead of having to manage the rendering of each component, there is now a single component to manage the rendering with some shared state, which in turn renders to two disparate parts of the DOM! If more components were added in the future, it’s as easy as creating a new portal and rendering it in the
<App /> component.
If the legacy application was ever converted to be a full-fledged React application, then the Portal components can be removed and the
<Chat> can remain untouched. This provides a very maintainable path going forward.
Portals provide a nice escape hatch to render components outside of the DOM hierarchy, while maintaining a consistent API that feels like any other React component. While this escape hatch could lead to potential misuse and problematic code, React
Portals provide an indispensable tool for building and maintaining applications in both new and legacy systems.
More Articles on React
1 Portals were built using
ReactDOM.render directly on a separate container or using
2 This modal is a trivial example to illustrate how to use
Portals. When building a modal for production, it is important to follow all accessibility guidelines or leverage an existing modal library that has first-class accessibility support.