For my project, I generate the content of a HTML landing page and then render it in an iFrame. Once it has finished loading, I allow my user to preview the page and then save and publish the generated content. This approach will also work with regular iFrame with a src
attribute if you want to load and check a specific URL. In that case, you will need to remove srcDoc
and sandbox
attributes and use the src
attribute instead.
IFrameRenderer Component
Let's create the IFrameRenderer component iframe-renderer.component.tsx. The IFrameRenderer component is a basic iFrame implementation. It accepts an iFrameRef
created in a parent component, this is the ref
we use to check the loaded status. The load
event which we will listen to is fired when the page has loaded, including all dependent resources such as stylesheets and images.
// iframe-renderer.component.tsx
import type { RefObject, FC } from 'react';
export const IFrameRenderer: FC<{
landingPageHtml: string;
iframeRef?: RefObject<HTMLIFrameElement>;
}> = ({ landingPageHtml, iframeRef }): JSX.Element => {
return (
<iframe
ref={iframeRef}
srcDoc={landingPageHtml}
title="iframe-preview"
width="100%"
height="700px"
sandbox
></iframe>
);
};
Note: The srcDoc
attribute is usually used with the sandbox
attribute. MDN Web Docs - The Inline Frame element.
An example parent component
Below is a simple example component that implements our newly created IFrameRenderer. We need a useState()
hook to maintain a value of true
or false
for displaying if the iFrame has finished loading.
// Example parent component
import type { FC } from 'react';
import { useState } from 'react';
import { IFrameRenderer } from './iframe-renderer.component';
export const IFramePreviewPage: FC = (): JSX.Element => {
const [isIFrameLoaded, setIsIFrameLoaded] = useState<boolean>(false);
const landingPageHtml =
'<p>This content is being injected into the iFrame.</p>';
return (
<div>
<p>iFrame is loaded: {String(isIFrameLoaded)}</p>
<IFrameRenderer landingPageHtml={landingPageHtml} />
</div>
);
};
In our parent component, we need to create a reference for the iFrame and pass it to our IFrameRenderer. Then we write a useEffect()
hook to change the isIFrameLoaded
state when the iFrame has finished loading. To detect when the iFrame has finished loading, we need to add an event listener to the iFrame, and we need to return the removeEventListener
function to remove the event listener if our component unmounts.
import type { FC } from 'react';
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import { IFrameRenderer } from './iframe-renderer.component';
export const IFramePreviewPage: FC = (): JSX.Element => {
const iFrameRef = useRef<HTMLIFrameElement>(null);
const [isIFrameLoaded, setIsIFrameLoaded] = useState<boolean>(false);
const iframeCurrent = iFrameRef.current;
useEffect(() => {
iframeCurrent?.addEventListener('load', () => setIFrameLoaded(true));
return () => {
iframeCurrent?.removeEventListener('load', () => setIFrameLoaded(true));
};
}, [iframeCurrent]);
const landingPageHtml =
'<p>This content is being injected into the iFrame.</p>';
return (
<div>
<p>iFrame is loaded: {String(isIFrameLoaded)}</p>
<IFrameRenderer landingPageHtml={landingPageHtml} />
<IFrameRenderer landingPageHtml={landingPageHtml} iFrameRef={iFrameRef} />
</div>
);
};
Convert to a custom hook
We have a working example, but we can do better by writing a reusable custom hook. Create a file called use-is-iframe-loaded.hook.ts and convert the code above to a Custom Hook.
// use-is-iframe-loaded.hook.ts
import type { RefObject } from 'react';
import { useState, useEffect } from 'react';
export const useIsIFrameLoaded = (
iframeRef: RefObject<HTMLIFrameElement>
): boolean => {
const [isIFrameLoaded, setIsIFrameLoaded] = useState<boolean>(false);
const iframeCurrent = iframeRef.current;
useEffect(() => {
iframeCurrent?.addEventListener('load', () => setIsIFrameLoaded(true));
return () => {
iframeCurrent?.removeEventListener('load', () => setIsIFrameLoaded(true));
};
}, [iframeCurrent]);
return isIFrameLoaded;
};
Final code usage
Now we have our custom hook, we can simplify our earlier parent component example.
// Example usage with custom hook
import type { FC } from 'react';
import { useRef } from 'react';
import { IFrameRenderer } from './iframe-renderer.component';
import { useIsIFrameLoaded } from './use-is-iframe-loaded.hook';
export const IFramePreviewPage: FC = (): JSX.Element => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const isIFrameLoaded = useIsIFrameLoaded(iframeRef);
const landingPageHtml =
'<p>This content is being injected into the iFrame.</p>';
return (
<div>
<p>iFrame is loaded: {String(isIFrameLoaded)}</p>
<IFrameRenderer landingPageHtml={landingPageHtml} iFrameRef={iFrameRef} />
</div>
);
};
That's a Wrap!
Now we have a custom hook that we can use for future projects, and we can also see a typical pattern for implementing custom hooks with different types of event listeners. If you found this tutorial useful, please drop a comment below.