Introduction to React Portals

Jake Borromeo
5 min readDec 27, 2022

--

React portals are a powerful tool that allows you to render a React component outside of its parent component. This can be useful in a variety of situations, such as:

  • Creating modals or dialogs
  • Adding notifications or alerts
  • Rendering elements in a different DOM context, such as in the <head> of the document

To use portals, you’ll need to install the react-dom package:

npm install react-dom

Then, you can use the ReactDOM.createPortal() method to create a portal. This method takes two arguments: the element to be rendered and the DOM element where the element should be rendered.

Here’s an example of a simple portal that renders a modal:

import React from 'react';
import ReactDOM from 'react-dom';
function Modal() {
return ReactDOM.createPortal(
<div className="modal">
<p>This is a modal</p>
</div>,
document.body
);
}

In this example, the Modal component is rendered in the <body> of the document, outside of its parent component.

Using Portals with State

To use portals with state, you’ll need to create a container component that manages the state and renders the portal. Here’s an example of a ModalContainer component that controls the visibility of the modal:

import React from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen }) {
return ReactDOM.createPortal(
<div className={`modal ${isOpen ? 'is-open' : ''}`}>
<p>This is a modal</p>
</div>,
document.body
);
}
import React, { useState } from 'react';
import Modal from './Modal';
function ModalContainer() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} />
</>
);
}

In this example, the ModalContainer component manages the isOpen state, which determines whether the modal is visible or not. When the user clicks the "Open Modal" button, the isOpen state is set to true, which causes the modal to be displayed.

Closing the Modal

To close the modal, you’ll need to add a way for the user to dismiss it. Here’s an example of a Modal component that includes a close button:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose }) {
return ReactDOM.createPortal(
<div className={`modal ${isOpen ? 'is-open' : ''}`}>
<button onClick={onClose}>Close</button>
<p>This is a modal</p>
</div>,
document.body
);
}
function ModalContainer() {
const [isOpen, setIsOpen] = useState(false);
function handleClose() {
setIsOpen(false);
}
return (
<>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} onClose={handleClose} />
</>
);
}

In this example, the Modal component includes a close button that calls the onClose prop when clicked. The ModalContainer component passes a handleClose function to the Modal component as the onClose prop, which sets the isOpen state to false and hides the modal.

Advanced Techniques

Here are a few advanced techniques for using portals:

Accessing the Portal Node

If you need to access the DOM node of the portal, you can use the ref prop:

import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen }) {
const modalRef = useRef();
return ReactDOM.createPortal(
<div className={`modal ${isOpen ? 'is-open' : ''}`} ref={modalRef}>
<p>This is a modal</p>
</div>,
document.body
);
}

In this example, the modalRef ref is attached to the <div> element that is being rendered as a portal. You can access the DOM node of the portal using the current property of the ref:

console.log(modalRef.current); // The DOM node of the portal

Updating the Portal Element

If you need to update the element being rendered in the portal, you can use the ReactDOM.unstable_renderSubtreeIntoContainer() method. This method allows you to update the element being rendered in the portal without unmounting and remounting the portal component.

Here’s an example of a Modal component that uses the unstable_renderSubtreeIntoContainer() method to update the element being rendered in the portal:

import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, children }) {
const modalRef = useRef();
const portalElement = useRef();
useEffect(() => {
portalElement.current = document.createElement('div');
document.body.appendChild(portalElement.current);
return () => {
document.body.removeChild(portalElement.current);
};
}, []);
useEffect(() => {
ReactDOM.unstable_renderSubtreeIntoContainer(
modalRef.current,
<div className={`modal ${isOpen ? 'is-open' : ''}`}>
<button onClick={onClose}>Close</button>
{children}
</div>,
portalElement.current
);
}, [isOpen, children]);
return ReactDOM.createPortal(
<div ref={modalRef} />,
portalElement.current
);
}

In this example, the Modal component uses the useEffect() hook to create a new DOM element when the component is mounted, and to remove the element when the component is unmounted. The unstable_renderSubtreeIntoContainer() method is used to update the element being rendered in the portal when the isOpen prop or the children prop changes.

Handling Keyboard Events

If you’re using portals to create modals or dialogs, you’ll likely want to handle keyboard events to allow the user to close the modal using the Escape key. Here's an example of a Modal component that handles keyboard events:

import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose }) {
const modalRef = useRef();
const portalElement = useRef();
useEffect(() => {
portalElement.current = document.createElement('div');
document.body.appendChild(portalElement.current);
return () => {
document.body.removeChild(portalElement.current);
};
}, []);
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown); return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [onClose]);
useEffect(() => {
ReactDOM.unstable_renderSubtreeIntoContainer(
import React, { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Modal({ isOpen, onClose }) {
const modalRef = useRef();
const portalElement = useRef();
useEffect(() => {
portalElement.current = document.createElement('div');
document.body.appendChild(portalElement.current);
return () => {
document.body.removeChild(portalElement.current);
};
}, []);
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown); return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [onClose]);
useEffect(() => {
ReactDOM.unstable_renderSubtreeIntoContainer(
modalRef.current,
<div className={`modal ${isOpen ? 'is-open' : ''}`}>
<button onClick={onClose}>Close</button>
<p>This is a modal</p>
</div>,
portalElement.current
);
}, [isOpen]);
return ReactDOM.createPortal(
<div ref={modalRef} />,
portalElement.current
);
}
function ModalContainer() {
const [isOpen, setIsOpen] = useState(false);
function handleOpen() {
setIsOpen(true);
}
function handleClose() {
setIsOpen(false);
}
return (
<>
<button onClick={handleOpen}>Open Modal</button>
<Modal isOpen={isOpen} onClose={handleClose}

Wrapping Up

React portals are a powerful tool that can be used in a variety of situations to render elements outside of their parent component. They’re especially useful for creating modals, dialogs, and notifications, and can be easily integrated into your React app with just a few lines of code.

I hope this article has helped you understand how portals work and how to use them effectively in your own projects. Happy coding!

--

--

No responses yet