In React 18, it was possible to use createPortal with a DOM element created by dangerouslySetInnerHTML.
Example (adapted from this Stack Overflow answer):
import { useCallback, useState } from "react";
import { createPortal } from "react-dom";
export default function App() {
const htmlFromElsewhere = `foo <span class="portal-container"></span> bar`;
return <InnerHtmlWithPortals html={htmlFromElsewhere} />
}
function InnerHtmlWithPortals({ html }: { html: string }) {
const [portalContainer, setPortalContainer] = useState<Element | null>(null)
const refCallback = useCallback((el: HTMLDivElement) => {
setPortalContainer(el?.querySelector(".portal-container"))
})
return <>
<div ref={refCallback} dangerouslySetInnerHTML={{ __html: html }} />
{portalContainer && createPortal(<Cake />, portalContainer)}
</>
}
function Cake() {
return <strong>cake</strong>
}
React 18 CodeSandbox: https://codesandbox.io/p/sandbox/optimistic-kowalevski-73sk5w
In React 19, this no longer works. React appears to be re-rendering the inner HTML after calling refCallback. Thus, createPortal succeeds, but the portalContainer element that it uses is no longer part of the DOM.
React 19 CodeSandbox: https://codesandbox.io/p/sandbox/vibrant-cloud-gd8yzr
It is possible to work around the issue by setting innerHTML directly instead of using dangerouslySetInnerHTML:
const [portalContainer, setPortalContainer] = useState<Element | null>(null)
const refCallback = useCallback((el: HTMLDivElement) => {
+ if (el) el.innerHTML = html
setPortalContainer(el?.querySelector(".portal-container"))
- })
+ }, [html])
return <>
- <div ref={refCallback} dangerouslySetInnerHTML={{ __html: html }} />
+ <div ref={refCallback} />
{portalContainer && createPortal(<Cake />, portalContainer)}
</>
But I'm not sure whether that is a reliable solution.
In React 18, it was possible to use
createPortalwith a DOM element created bydangerouslySetInnerHTML.Example (adapted from this Stack Overflow answer):
React 18 CodeSandbox: https://codesandbox.io/p/sandbox/optimistic-kowalevski-73sk5w
In React 19, this no longer works. React appears to be re-rendering the inner HTML after calling
refCallback. Thus,createPortalsucceeds, but theportalContainerelement that it uses is no longer part of the DOM.React 19 CodeSandbox: https://codesandbox.io/p/sandbox/vibrant-cloud-gd8yzr
It is possible to work around the issue by setting
innerHTMLdirectly instead of usingdangerouslySetInnerHTML:const [portalContainer, setPortalContainer] = useState<Element | null>(null) const refCallback = useCallback((el: HTMLDivElement) => { + if (el) el.innerHTML = html setPortalContainer(el?.querySelector(".portal-container")) - }) + }, [html]) return <> - <div ref={refCallback} dangerouslySetInnerHTML={{ __html: html }} /> + <div ref={refCallback} /> {portalContainer && createPortal(<Cake />, portalContainer)} </>But I'm not sure whether that is a reliable solution.