React Portals

By

Learn how React Portals render an element outside its component hierarchy with createPortal from react-dom, a perfect fit for modal windows styled at full screen.

~~~

React 16, released in September 2017, introduced Portals. They work the same way today, and I checked the code in this post with React 19.

A portal is a way to render an element outside of its component hierarchy, in a separate DOM node.

When that element is rendered, events happening on it are managed by the React components hierarchy rather than by the hierarchy set by the DOM position of the element.

Hence the name “portal”: an element sits somewhere in the DOM tree that’s outside of the normal React components tree, but the React component tree that includes it is still in charge.

React offers an easy API to do this, createPortal() from react-dom, which accepts 2 arguments. The first is the element to render, the second is the DOM element where to render it.

A classic use case for this is modal windows.

A modal to render at full screen must live outside of the element, so it can be properly styled using CSS.

So if a modal is defined as a component:

import React from 'react'
import { createPortal } from 'react-dom'

class Modal extends React.Component {
  constructor(props) {
    super(props)
    this.el = document.createElement('div')
  }

  componentDidMount() {
    document.getElementById('modal').appendChild(this.el)
  }

  componentWillUnmount() {
    document.getElementById('modal').removeChild(this.el)
  }

  render() {
    return createPortal(
      this.props.children,
      this.el
    )
  }
}

We can have an App component render it, and all the events happening in the Modal component will be handled by App even though technically the modal is rendered in a different DOM tree. The page needs both an app and a modal element in the HTML:

import React from 'react'
import { createRoot } from 'react-dom/client'

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {showModal: false}

    this.handleShow = this.handleShow.bind(this)
    this.handleHide = this.handleHide.bind(this)
  }

  handleShow() {
    this.setState({showModal: true})
  }

  handleHide() {
    this.setState({showModal: false})
  }

  render() {
    const modal = this.state.showModal ? (
      <Modal>
        <div>
          The modal <button onClick={this.handleHide}>Hide</button>
        </div>
      </Modal>
    ) : ''

    return (
      <div>
        The app <button onClick={this.handleShow}>Show modal</button>
        {modal}
      </div>
    )
  }
}

createRoot(document.getElementById('app')).render(<App />)

See the full example on https://codepen.io/flaviocopes/pen/KbdagX. The pen is the original one, so it runs on React 16 and mounts with ReactDOM.render(), which React 19 removed in favor of createRoot(). The Modal component is the same.

Tagged: React · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about react: