31st October 2017
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
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:
showSidebarwhich will toggle the sidebar open and closed. To make this work, we will have to update our
App.cssfile to use the translate transform to slide the div in and out of the screen:
Toggle Sidebar. 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:
transformproperty to the sidebar changed our fixed position from the viewport to the sidebar container! This means the styling of the
<Modal />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
<Modal />component brittle to work with. We really want to maximize re-use and want the
<Modal />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
React.createPortalexpects two arguments, the first being the Component you want rendered (in this case our
<Modal />component) and the second being a DOM element (
this.containerin our example). In
componentDidMountwe manually append the element into the DOM and on
componentWillUnmount, the element is removed from the DOM.
App.js, let’s add code to display our new
<Portal /> component alongside the
<Modal /> component:
Show Portalbelow the
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:
<Modal />is rendered completely outside our
<App />component. This means that regardless of the styling of our
<App />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
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.
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
- How to use GraphQL in React using hooks
- Speeding up React SSR: Announcing ESX
- Animation in React
- Say Hello to React Browser Hooks
- Forget Everything you Learned about React – Hooks Rock!
- Managing React State with Render Props
- Exploring React Portals
- Sharing React Components with Lerna
- React Components Living Style Guides Overview
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.