Skip to content

Instantly share code, notes, and snippets.

@threepointone
Created December 8, 2020 00:16
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save threepointone/e73a87f7bbbebc78cf71744469ec5a15 to your computer and use it in GitHub Desktop.
Save threepointone/e73a87f7bbbebc78cf71744469ec5a15 to your computer and use it in GitHub Desktop.
An iframe loader powered by Suspense
import { Suspense, useLayoutEffect, useRef, useState } from 'react';
type IFrameProps = React.ComponentPropsWithRef<'iframe'> & {
fallback?: JSX.Element;
};
export function IFrame(props: IFrameProps) {
const { fallback, ...rest } = props;
return (
// @ts-expect-error
<Suspense unstable_avoidThisFallback={fallback || 'loading...'}>
<IFrameImplementation {...rest} />
</Suspense>
);
}
function IFrameImplementation(props: React.ComponentPropsWithRef<'iframe'>) {
const awaiter = useRef<null | {
promise: null | Promise<void>;
resolve: () => void;
reject: () => void;
}>(null);
const [_, triggerLoad] = useState(false)
if (awaiter.current?.promise) {
console.log('suspend')
throw awaiter.current.promise;
}
useLayoutEffect(() => {
if (awaiter.current === null) {
// @ts-ignore
awaiter.current = {}
// @ts-ignore
awaiter.current.promise = new Promise<void>((resolve, reject) => {
Object.assign(awaiter.current, { resolve, reject });
});
triggerLoad(true)
}
}, []);
const { title } = props;
return (
<iframe
{...props}
title={title}
onLoad={(e) => {
// @ts-ignore
awaiter.current.promise = null;
awaiter.current?.resolve();
props.onLoad?.(e);
}}
onError={(err) => {
// @ts-ignore
awaiter.current.promise = null;
awaiter.current?.reject();
props.onError?.(err);
}}
/>
);
}
export default IFrame;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment