the preview painted nothing, because its ref was rebuilt every render
The iframe's ref was an inline callback that reset `loaded` to 0 when called with null. React treats an inline ref as a DIFFERENT ref on every render, so it detaches and reattaches each time — calling the callback with null, then the node. So `loaded` went back to 0 on every render, which re-rendered, which reset it again. The injection never survived long enough to run and the page came up with no controls at all. An object ref again, and the reset moved into an effect keyed on `reloadToken`, which is the thing that actually means "a new document is coming". Same lesson as `useClient` two files along, and the third time this session: a value rebuilt every render must not be the thing something else keys on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-6
@@ -185,6 +185,22 @@ export const SitePreview = ({
|
||||
*/
|
||||
const [loaded, setLoaded] = useState(0);
|
||||
|
||||
/*
|
||||
A remount is a NEW document, so `loaded` has to go back to nothing before it arrives.
|
||||
|
||||
In an effect keyed on the token, and NOT in the iframe's ref. It was a ref callback, written inline
|
||||
— which React treats as a different ref every render, so it detaches and reattaches on each one,
|
||||
calling the callback with null and then the node. That reset `loaded` to 0 every render, which
|
||||
re-rendered, which reset it again: the paint never survived long enough to happen and the page came
|
||||
up with no controls at all.
|
||||
|
||||
The lesson is the same one as `useClient` two files along: a value rebuilt every render must not be
|
||||
the thing something else keys on.
|
||||
*/
|
||||
useEffect(() => {
|
||||
setLoaded(0);
|
||||
}, [reloadToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loaded) return;
|
||||
const frameDocument = frame.current?.contentDocument;
|
||||
@@ -244,12 +260,7 @@ export const SitePreview = ({
|
||||
// The key is what makes `reloadToken` reload: a new key is a new element, so the browser
|
||||
// fetches the document again and `onLoad` runs, which re-injects the builder's chrome.
|
||||
key={reloadToken ?? 0}
|
||||
// A remount is a NEW document, and the effect above must not paint into the old one on
|
||||
// the strength of a `loaded` that referred to it.
|
||||
ref={(node) => {
|
||||
frame.current = node;
|
||||
if (!node) setLoaded(0);
|
||||
}}
|
||||
ref={frame}
|
||||
// Applied on load as well as on change: the document is replaced on a reload, and the
|
||||
// properties go with it.
|
||||
onLoad={() => {
|
||||
|
||||
Reference in New Issue
Block a user