Introduction to React Portals
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!