diff --git a/src/components/CodeEditor/index.tsx b/src/components/CodeEditor/index.tsx index a582b596a..db1887e72 100644 --- a/src/components/CodeEditor/index.tsx +++ b/src/components/CodeEditor/index.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { Button, CopyText, Icon, Select } from "@stellar/design-system"; -import MonacoEditor, { useMonaco, type OnMount } from "@monaco-editor/react"; +import MonacoEditor, { type OnMount, loader } from "@monaco-editor/react"; import { useStore } from "@/store/useStore"; import { Box } from "@/components/layout/Box"; @@ -47,10 +47,16 @@ export const CodeEditor = ({ maxHeightInRem, }: CodeEditorProps) => { const { theme } = useStore(); - const monaco = useMonaco(); const headerEl = useRef(null); - const [isReady, setIsReady] = useState(false); + const [isReady, setIsReady] = useState( + // If Monaco is already loaded (e.g., another editor was mounted first), + // skip the async init to avoid a flicker. + () => { + const getMonacoInstance = (loader as any).__getMonacoInstance; + return typeof getMonacoInstance === "function" && getMonacoInstance() !== null; + }, + ); const [isExpanded, setIsExpanded] = useState(false); // Default header height is 43px @@ -58,11 +64,26 @@ export const CodeEditor = ({ const [autoHeight, setAutoHeight] = useState(null); + // Load Monaco without the unhandled-rejection bug that exists in the + // useMonaco() hook shipped by @monaco-editor/react: its internal + // loader.init().then(...) call has NO .catch(), so when React StrictMode + // double-invokes effects the cleanup calls loader.cancel() which rejects + // the promise with a plain { type: 'cancelation' } object, surfacing as + // '[object Object]' in Next.js's onUnhandledRejection handler. useEffect(() => { - if (typeof window !== "undefined" && monaco) { - setIsReady(true); - } - }, [monaco]); + if (isReady) return; + + const initPromise = loader.init(); + initPromise + .then(() => setIsReady(true)) + // Swallow both intentional cancellations and unexpected errors so the + // promise rejection never escapes to the global unhandledrejection event. + .catch(() => {}); + + return () => { + initPromise.cancel(); + }; + }, [isReady]); if (!isReady) { return null;