A dive into React Portals and exploring some common use cases
31 Oct 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 . Portals have been a concept in the React community for quite some time 1 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 modal 2 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 Modal.css in src/
Next, update App.js with a basic button to toggle the <Modal /> component:
and add the following css to src/App.css :
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 showSidebar which will toggle the sidebar open and closed. To make this work, we will have to update our App.css 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 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:
Adding the transform property 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 public/index.html called root-modal right before the closing </body> tag.
Next, let's create a new file called src/Portal.js :
React.createPortal expects two arguments, the first being the Component you want rendered (in this case our <Modal /> component) and the second being a DOM element ( this.container in our example). In componentDidMount we manually append the element into the DOM and on componentWillUnmount , the element is removed from the DOM.
In App.js , let's add code to display our new <Portal /> component alongside the <Modal /> component:
and the corresponding css in App.css :
There should be a new button called Show Portal below the Show Modal button:
When clicking Show Modal , the application should still appear broken, as transform is still set on the sidebar.
After clicking CLOSE , click on Show Portal .
Success! Our <Modal /> component is back to normal. To see why, let's examine the HTML:
Notice how the <Modal /> is rendered completely outside our <App /> component. This means that regardless of the styling of our <App /> component, it will not break the <Modal /> component.
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.
The <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 ReactDOM.render
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 <Profile> and <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.
1 Portals were built using ReactDOM.render directly on a separate container or using ReactDOM.unstable_renderSubtreeIntoContainer
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.
Insight, imagination and expertly engineered solutions to accelerate and sustain progress.