React's Portals in 3 Minutes

Dec 18, 2019
React, Javascript
~3min
Share on Twitter

What is it?

An API for rendering components outside of your app’s DOM hierarchy.

ReactDOM.createPortal(<Component/>, DOMElement)

For those in camp TL;DR scroll down for a demo!

Why?

Perfect for scenarios where CSS styles are restraining your elements. For example, stacking(z-index) and overflow issues. You could even render things in a new window! 😎

Check out this HackerNoon article about rendering in separate windows!

How?

Instead of returning an element in a component’s render method, return a portal.

const Outsider = () => ReactDom.createPortal(<div>I am outside</div>, document.body)

const App = () => <Outsider/>

Outsider renders as a direct descendant of document.body 👍

When to use?

  • Modals
  • Tooltips
  • Floating menus
  • Widgets

Scope + Bubbling

A brilliant thing about portals is that a component rendered in a portal acts as if it is still in the React tree. It behaves like a normal React child. Pass it props, it will react to updates, etc.

Events fired in a portal will also bubble up through the React tree! Check out the example in the React docs.

Basic example (Modal)

Let’s start with a common use case — the Modal. Modals are a great example of where we may need to render a component outside of the current DOM structure.

Our Modal will render based on a state value in the app.

const Modal = ({ children, onClose, open }) =>
  open
    ? ReactDOM.createPortal(
      <div className='modal'>
        <button className='modal__close' onClick={onClose}>&times;</button>
        { children }
      </div>,
      document.body
    )
  : null

For our example, we will render the Modal on document.body. Our Modal is a functional component that accepts children, onClose and open as props.

Here it is in action!

Check out this pen by Jhey (@jh3y) on CodePen.

A silly example

Remember the video game "Portal"?

Let’s create a scene 😅

Let’s start with a Man 🏃. We are going to use Greensock's "Draggable" to create a draggable Man.

Now let’s create a scene with a "Portal". Our man will be bound by the app container.

const App = () => (
  <Fragment>
    <Man bounds={rootNode} />
    <div className="portal portal--in"/>
  </Fragment>
)

That gives us

Draggable Man concept where the Man Component is bound to a container

Now let’s get ReactDOM.createPortal involved 😃

We add an element into the DOM outside of our app (#outside). We also create state for tracking whether our Man is in or out of the app container.

We then use createPortal to render a Portal in #outside. And if outside is true we will use createPortal to render our Man in that outer element 👍

<Man
  bounds={outside ? outsideElement : rootNode}
  onRelease={onRelease}
  outside={outside}
/>
<div className="portal portal--in" ref={innerPortalRef} />
{createPortal(
  <div ref={outerPortalRef} className="portal portal--out" />,
  outsideElement
)}
const ManRender = () => (
  <div className="man" ref={manRef} role="img">
    🏃
  </div>
);

return outside ? createPortal(<ManRender />, bounds) : <ManRender />;

Our Man now invokes an onRelease function too. This checks our cursor position against our portal bounds on release. If we release over a portal, we toggle the state value. All the code is in the demo, there's not much to it 👍

Clip of the working portal. Man component can be dragged in and out of the app container via the fictional portal

If you use your dev tools to inspect the DOM, you’ll see the render happening outside #app 👍

Check out this pen by Jhey (@jh3y) on CodePen.

Notes

  • Don’t neglect Accessibility! Maintaining keyboard focus etc. is very important.
  • Available in React@16.0.0+

That’s it!

A 3-minute intro to portals in React!