React Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This feature is particularly useful for cases where you need to render content that visually "breaks out" of its container, such as modals, tooltips, and popovers. This chapter will cover everything you need to know about React Portals, from the basics to advanced use cases, ensuring a comprehensive understanding.
In web development, there are instances where we need to render components outside the normal DOM hierarchy. For example:
Rendering such components within the normal DOM hierarchy can cause issues with styling and positioning. Without portals, you might have to deal with CSS z-index or position conflicts.
React Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This allows components to visually break out of their containers while maintaining their logical parent-child relationship in React’s component tree.
Creating a portal involves using the ReactDOM.createPortal
method.
ReactDOM.createPortal(child, container)
App Structure
React Portal Example
// src/App.js
import React from 'react';
import ReactDOM from 'react-dom';
import Modal from './Modal';
function App() {
const [showModal, setShowModal] = React.useState(false);
const toggleModal = () => setShowModal(!showModal);
return (
React Portal Example
{showModal && }
);
}
export default App;
// src/Modal.js
import React from 'react';
import ReactDOM from 'react-dom';
const modalRoot = document.getElementById('portal-root');
function Modal() {
return ReactDOM.createPortal(
Modal Title
This is a modal rendered using a React Portal.
,
modalRoot
);
}
const modalStyle = {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
padding: '20px',
backgroundColor: 'white',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
zIndex: 1000,
};
export default Modal;
div
with id portal-root
in index.html
serves as the container for the portal.Modal
component uses ReactDOM.createPortal
to render its children into the portal-root
container.When you click the “Show Modal” button, a modal will appear centered on the screen, overlaying other content. Clicking the button again hides the modal.
Portals can handle more complex structures and components.
// src/NestedPortal.js
import React from 'react';
import ReactDOM from 'react-dom';
const portalRoot = document.getElementById('portal-root');
function NestedPortal() {
return ReactDOM.createPortal(
Nested Portal
This portal contains nested components.
,
portalRoot
);
}
function NestedComponent() {
return I am a nested component inside the portal.;
}
const portalStyle = {
position: 'fixed',
top: '40%',
left: '50%',
transform: 'translate(-50%, -40%)',
padding: '20px',
backgroundColor: 'lightblue',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
zIndex: 1000,
};
const nestedStyle = {
marginTop: '10px',
padding: '10px',
backgroundColor: 'white',
border: '1px solid #ccc',
};
export default NestedPortal;
NestedPortal
renders NestedComponent
inside the portal.When NestedPortal
is rendered, it shows a styled box containing another styled component, all rendered outside the normal DOM hierarchy.
You can create multiple portals in a single application, each rendering to different DOM nodes.
// src/MultiplePortals.js
import React from 'react';
import ReactDOM from 'react-dom';
const portalRoot1 = document.getElementById('portal-root1');
const portalRoot2 = document.getElementById('portal-root2');
function MultiplePortals() {
return (
<>
{ReactDOM.createPortal(
Portal 1
Content for portal 1.
,
portalRoot1
)}
{ReactDOM.createPortal(
Portal 2
Content for portal 2.
,
portalRoot2
)}
>
);
}
const portalStyle1 = {
position: 'fixed',
top: '30%',
left: '50%',
transform: 'translate(-50%, -30%)',
padding: '20px',
backgroundColor: 'lightcoral',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
zIndex: 1000,
};
const portalStyle2 = {
position: 'fixed',
top: '60%',
left: '50%',
transform: 'translate(-50%, -60%)',
padding: '20px',
backgroundColor: 'lightgreen',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
zIndex: 1000,
};
export default MultiplePortals;
portal-root1
and portal-root2
) are used to render different content.Two different pieces of content are rendered at different positions on the screen, each managed by a separate portal.
Events in portals propagate through the React component tree, even if they are not in the same DOM tree.
// src/ModalWithEvents.js
import React from 'react';
import ReactDOM from 'react-dom';
const portalRoot = document.getElementById('portal-root');
function ModalWithEvents({ onClose }) {
return ReactDOM.createPortal(
e.stopPropagation()}>
Modal with Events
Click outside to close.
,
portalRoot
);
}
const modalStyle = {
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
};
const modalContentStyle = {
padding: '20px',
backgroundColor: 'white',
borderRadius: '5px',
};
export default ModalWithEvents;
onClose
handler.stopPropagation
prevents the click event from bubbling up to the parent.A modal with an overlay that closes when you click outside the modal content.
React Portal Tooltip
// src/App.js
import React from 'react';
import Tooltip from './Tooltip';
function App() {
return (
React Portal Tooltip Example
);
}
export default App;
// src/Tooltip.js
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const portalRoot = document.getElementById('portal-root');
function Tooltip({ children, text }) {
const [visible, setVisible] = useState(false);
const [position, setPosition] = useState({ top: 0, left: 0 });
const ref = useRef();
useEffect(() => {
const handleMouseOver = () => {
const rect = ref.current.getBoundingClientRect();
setPosition({
top: rect.top + window.scrollY - 30,
left: rect.left + window.scrollX,
});
setVisible(true);
};
const handleMouseOut = () => {
setVisible(false);
};
const node = ref.current;
node.addEventListener('mouseover', handleMouseOver);
node.addEventListener('mouseout', handleMouseOut);
return () => {
node.removeEventListener('mouseover', handleMouseOver);
node.removeEventListener('mouseout', handleMouseOut);
};
}, []);
return (
<>
{React.cloneElement(children, { ref })}
{visible &&
ReactDOM.createPortal(
{text}
,
portalRoot
)}
>
);
}
const tooltipStyle = {
position: 'absolute',
backgroundColor: 'black',
color: 'white',
padding: '5px',
borderRadius: '3px',
fontSize: '12px',
zIndex: 1000,
};
export default Tooltip;
A button with a tooltip that appears when you hover over it, demonstrating the use of portals for creating tooltips.
React Portals provide a powerful mechanism for rendering components outside the normal DOM hierarchy, making them ideal for creating overlays, modals, tooltips, and other UI elements that need to visually break out of their parent containers. By understanding and utilizing React Portals, you can create more flexible and maintainable UIs.Happy coding !❤️