Skip to content

Instantly share code, notes, and snippets.

@marks
Created February 6, 2026 22:19
Show Gist options
  • Select an option

  • Save marks/5df562f570fc73566e468aaa96cfedfc to your computer and use it in GitHub Desktop.

Select an option

Save marks/5df562f570fc73566e468aaa96cfedfc to your computer and use it in GitHub Desktop.
import React from 'react'
import { type FC } from 'react'
import { Retool } from '@tryretool/custom-component-support'
export const IframeReader: FC = () => {
const iframeRef = React.useRef<HTMLIFrameElement>(null);
const [error, setError] = React.useState<string | null>(null);
const observerRef = React.useRef<MutationObserver | null>(null);
const intervalRef = React.useRef<number | null>(null);
React.useEffect(() => {
const iframe = iframeRef.current;
if (!iframe) return;
let retryTimeout: number | null = null;
let retryCount = 0;
const maxRetries = 30; // ~6 seconds (200ms interval)
const retryInterval = 200;
const setupObservers = (el: HTMLElement) => {
const doc = iframe.contentDocument;
iframe.style.height = el.clientHeight + 'px';
setError(null);
// MutationObserver for height changes
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new MutationObserver(() => {
const h = el.clientHeight;
iframe.style.height = h + 'px';
});
observerRef.current.observe(el, { attributes: true, childList: true, subtree: true });
// Polling fallback for height changes
if (intervalRef.current) window.clearInterval(intervalRef.current);
let lastH = el.clientHeight;
intervalRef.current = window.setInterval(() => {
const h = el.clientHeight;
if (h !== lastH) {
iframe.style.height = h + 'px';
lastH = h;
}
}, 300);
};
const tryFindElement = () => {
try {
const doc = iframe.contentDocument;
if (!doc) {
if (retryCount < maxRetries) {
retryCount++;
retryTimeout = window.setTimeout(tryFindElement, retryInterval);
} else {
setError('contentDocument is null');
}
return;
}
const el = doc.querySelector("[data-testid='RetoolGrid::Main']");
if (!el) {
if (retryCount < maxRetries) {
retryCount++;
retryTimeout = window.setTimeout(tryFindElement, retryInterval);
} else {
setError("Element [data-testid='RetoolGrid::Main'] not found");
}
return;
}
setupObservers(el as HTMLElement);
} catch (e: any) {
setError('Unable to observe client size: ' + (e?.message || e));
}
};
const onLoad = () => {
retryCount = 0;
tryFindElement();
};
iframe.addEventListener('load', onLoad);
// If already loaded, call onLoad immediately
if (iframe.contentDocument?.readyState === 'complete') {
onLoad();
}
return () => {
iframe.removeEventListener('load', onLoad);
if (observerRef.current) observerRef.current.disconnect();
if (intervalRef.current) window.clearInterval(intervalRef.current);
if (retryTimeout) window.clearTimeout(retryTimeout);
};
}, []);
return (
<div>
<iframe
ref={iframeRef}
src="https://...?_hideNav=true"
width="100%"
height="100px"
title="External Content"
style={{ border: '1px solid #ccc', marginBottom: 8 }}
/>
{error && <div style={{ color: 'red' }}>{error}</div>}
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment