diff --git a/src/Builder.tsx b/src/Builder.tsx index c427b8d..e53ef54 100644 --- a/src/Builder.tsx +++ b/src/Builder.tsx @@ -28,6 +28,7 @@ const Builder = (props: BuilderPropsWithCallbacks) => { const configRegistry = getConfigRegistry() const [editorReady, setEditorReady] = useState(false) const instanceRef = useRef(null) + const startedRef = useRef(false) const config = useMemo(() => { const configFromRegistry = configRegistry.get(container) || {} @@ -41,14 +42,18 @@ const Builder = (props: BuilderPropsWithCallbacks) => { }, [configRegistry, container, callbacks]) useEffect(() => { - if (editorReady) { - instanceRef.current?.loadConfig?.(callbacks).catch((error) => { - if (error.code === 3001) { - callbacksRef.current.onWarning?.(error) - } else { - callbacksRef.current.onError?.({ code: 1000, message: `Error updating builder config: ${error}` }) - } - }) + if (editorReady && startedRef.current) { + try { + instanceRef.current?.loadConfig?.(callbacks).catch((error) => { + if (error.code === 3001) { + callbacksRef.current.onWarning?.(error) + } else { + callbacksRef.current.onError?.({ code: 1000, message: `Error updating builder config: ${error}` }) + } + }) + } catch (error) { + callbacksRef.current.onError?.({ code: 1000, message: `Error updating builder config: ${error}` }) + } } }, [callbacks, container, editorReady]) @@ -73,49 +78,62 @@ const Builder = (props: BuilderPropsWithCallbacks) => { } }, [token]) - // Creates and starts SDK instance - if (instanceRef.current === null && token) { + // Construct/start in an effect (not render) so a `disposed` flag in closure + // can suppress stale start() resolutions from a prior StrictMode mount — + // otherwise loadConfig fires on a not-yet-started instance and throws + // "Bee is not started" synchronously from a passive effect. + useEffect(() => { + if (!token) return if (!config.uid) { callbacksRef.current.onError?.({ message: `Can't start the builder without a uid in the config`, }) - } else { - instanceRef.current = new BeefreeSDK(token, { - beePluginUrl: loaderUrl, - ...(process.env.NPM_PACKAGE_NAME && process.env.NPM_PACKAGE_VERSION - ? { - wrapperInfo: { - packageName: process.env.NPM_PACKAGE_NAME, - packageVersion: process.env.NPM_PACKAGE_VERSION, - }, - } - : {}), - }) + return + } - const beeInstance = instanceRef.current + let disposed = false - if (shared && sessionId) { - void beeInstance.join(config, sessionId, bucketDir).then(() => { - setEditorReady(true) - }).catch((error) => { - config.onError?.({ - message: `Error joining the shared session: ${error}`, - }) + instanceRef.current = new BeefreeSDK(token, { + beePluginUrl: loaderUrl, + ...(process.env.NPM_PACKAGE_NAME && process.env.NPM_PACKAGE_VERSION + ? { + wrapperInfo: { + packageName: process.env.NPM_PACKAGE_NAME, + packageVersion: process.env.NPM_PACKAGE_VERSION, + }, + } + : {}), + }) + + const beeInstance = instanceRef.current + + if (shared && sessionId) { + void beeInstance.join(config, sessionId, bucketDir).then(() => { + if (disposed) return + startedRef.current = true + setEditorReady(true) + }).catch((error) => { + if (disposed) return + config.onError?.({ + message: `Error joining the shared session: ${error}`, }) - } else { - void beeInstance.start(config, template, bucketDir, { shared }).then(() => { - setEditorReady(true) - }).catch((error) => { - config.onError?.({ - message: `Error starting the builder: ${error}`, - }) + }) + } else { + void beeInstance.start(config, template, bucketDir, { shared }).then(() => { + if (disposed) return + startedRef.current = true + setEditorReady(true) + }).catch((error) => { + if (disposed) return + config.onError?.({ + message: `Error starting the builder: ${error}`, }) - } + }) } - } - useEffect(() => { return () => { + disposed = true + startedRef.current = false // SDK doesn't provide a destroy/dispose method, so we manually clean up // by clearing the container's content (removes iframe and event listeners) const containerElement = document.getElementById(container) @@ -128,7 +146,8 @@ const Builder = (props: BuilderPropsWithCallbacks) => { } setEditorReady(false) } - }, [container]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token, container]) return (