How to Update React Portals in React
React Portals provide a powerful way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This article explains how to dynamically update the state, props, and content of React Portals, ensuring seamless communication and synchronization between the portal and your main React application.
Understanding React Portal Updates
Although a portal is rendered into a different physical DOM node
(often at the end of the <body> tag), it still
behaves like a standard React child in every other way. It remains
positioned inside the React virtual DOM tree.
Because the portal remains in the React tree, updating a React Portal is identical to updating any standard React component. It retains access to the React Context, receives props, and triggers re-renders whenever the parent component’s state changes.
1. Updating Portals via Parent State and Props
The most common way to update a portal is by passing dynamic props or state from the parent component. When the parent component’s state changes, React automatically re-renders the portal with the updated data.
Here is an example of a modal portal that updates its content based on the parent component’s state:
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
// The Portal Component
const ModalPortal = ({ isOpen, message, onClose }) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal-overlay">
<div className="modal-content">
<p>{message}</p>
<button onClick={onClose}>Close</button>
</div>
</div>,
document.getElementById('portal-root')
);
};
// The Parent Component
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const [counter, setCounter] = useState(0);
return (
<div className="app">
<h1>React Portal Update Example</h1>
<button onClick={() => setIsOpen(true)}>Open Portal</button>
<button onClick={() => setCounter(counter + 1)}>Increment Counter ({counter})</button>
{/* The Portal updates automatically when 'counter' or 'isOpen' changes */}
<ModalPortal
isOpen={isOpen}
message={`The current count is: ${counter}`}
onClose={() => setIsOpen(false)}
/>
</div>
);
}In this example, every time the “Increment Counter” button is
clicked, the parent state counter updates. React propagates
this change down to the ModalPortal prop
message, forcing the portal to update its visible DOM
content immediately.
2. Updating Parent State from inside the Portal (Event Bubbling)
Even though a portal is rendered outside the parent’s DOM node, events fired from inside the portal still bubble up through the React virtual DOM tree.
This means you can capture events triggered inside the portal at the parent level to update your application state.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
const ClickPortal = () => {
return ReactDOM.createPortal(
<div className="portal-button-container">
<button>Click Me to Trigger Parent Event</button>
</div>,
document.getElementById('portal-root')
);
};
export default function App() {
const [clicks, setClicks] = useState(0);
// This handler catches clicks from the portal due to React event bubbling
const handlePortalClick = () => {
setClicks((prev) => prev + 1);
};
return (
<div onClick={handlePortalClick} className="parent-container">
<p>Portal button clicks captured by parent: {clicks}</p>
<ClickPortal />
</div>
);
}Because of React’s event system, clicking the button inside
ClickPortal bubbles up to the div wrapper in
App, updating the clicks state and
subsequently updating the UI.
3. Dynamically Changing the Portal Destination
In rare scenarios, you might need to move the portal from one DOM
element to another dynamically. You can achieve this by storing the
target DOM node in a state variable and passing it to
ReactDOM.createPortal.
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
const DynamicDestinationPortal = ({ children }) => {
const [targetNode, setTargetNode] = useState(null);
useEffect(() => {
// Dynamically choose or change target destination based on conditions
const target = document.getElementById('dynamic-container-id') || document.body;
setTargetNode(target);
}, []);
if (!targetNode) return null;
return ReactDOM.createPortal(children, targetNode);
};By managing the target DOM container within React’s lifecycle, the
portal will tear itself down from the old DOM location and remount in
the new one whenever the targetNode state is updated.