I have a component that calls the document
global. When I render this component in an iframe, it uses the current (parent) document as a reference, not the document of the iframe.
export const Modal: FC<ModalProps> = ({ opened, ...props }) => {
// ...
useEffect(() => {
// Prevent background scrolling when the modal is opened.
// This line locks scroll on the main DOM, but I only want it to be blocked on the iframe.
document.body.style.overflow = opened ? "hidden" : "";
}, [opened]);
// ...
}
Can I render the Modal
component, so that document
refers to the iframe document (and not the main DOM) ?
I use this custom component to render React elements in a separate iframe:
import { FC, DetailedHTMLProps, IframeHTMLAttributes, useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
export const IFrame: FC<DetailedHTMLProps<IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement>> = ({ children, style, ...props }) => {
const [contentRef, setContentRef] = useState<HTMLIFrameElement | null>(null);
const headNode = useMemo(() => contentRef?.contentWindow?.document?.head, [contentRef]);
const mountNode = useMemo(() => contentRef?.contentWindow?.document?.body, [contentRef]);
// Setup head.
useEffect(() => {
if (headNode) {
// Inherit parent styles (like compiled css modules or local fonts).
for (const style of Array.from(document.head.querySelectorAll("link"))) {
headNode.appendChild(style.cloneNode(true));
}
for (const style of Array.from(document.head.querySelectorAll("style"))) {
headNode.appendChild(style.cloneNode(true));
}
}
}, [headNode]);
return (
<>
<iframe
{...props}
style={{ border: "none", ...style }}
ref={setContentRef}
/>
{mountNode ? createPortal(children, mountNode) : null}
</>
);
});
I also tried to create a new root and render the children inside, but it doesn't work either.
useEffect(() => {
if (mountNode) {
const root = createRoot(mountNode);
root.render(children);
return () => {
root.unmount();
};
}
}, [mountNode]);
return (
<iframe
{...props}
style={{ border: "none", ...style }}
ref={setContentRef}
/>
);
As no one else has had a go at this, please excuse a little speculation in this answer.
What I think is happening here is that when you put content inside react’s <iframe>
element it is creating your content using the srcdoc
attribute, rather than the more normal src
. This is then leading to your iframed content to exist in the same DOM context as your parent page.
To fix this I think you either need to force React to load a new page into your iframe, or instead store the contextual data your after inside this component and pass a setState method to your child nodes.