From 3eefb4c886612a7d473b6343c127d6392af4f2de Mon Sep 17 00:00:00 2001 From: skyash-dev Date: Sat, 6 Jun 2026 13:42:49 +0530 Subject: [PATCH 1/2] fix stale react state in cm6 update listener --- .../IDE/components/Editor/codemirror.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/client/modules/IDE/components/Editor/codemirror.js b/client/modules/IDE/components/Editor/codemirror.js index c2c1dc1c48..1b72a17dae 100644 --- a/client/modules/IDE/components/Editor/codemirror.js +++ b/client/modules/IDE/components/Editor/codemirror.js @@ -49,18 +49,30 @@ export default function useCodeMirror({ const fileId = useRef(); fileId.current = file.id; + // CM6 update listeners are attached when file states are created and + // can outlive React renders. Keep dynamic values in a ref so onChange + // always sees the latest state. + const latestRef = useRef({}); + latestRef.current = { + autorefresh, + project, + files + }; + // When the file changes, update the file content and save status. function onChange() { + const latest = latestRef.current; + setUnsavedChanges(true); updateFileContent(fileId.current, cmView.current.state.doc.toString()); // Save a local backup to localStorage for crash recovery (#3891). // This ensures work is recoverable even if the tab crashes // (e.g. from an infinite loop) before the server autosave fires. - const projectId = project?.id || 'unsaved'; - saveLocalBackup(projectId, files); + const projectId = latest.project?.id || 'unsaved'; + saveLocalBackup(projectId, latest.files); - if (autorefresh) { + if (latest.autorefresh) { clearConsole(); startSketch(); } From 206e716320a68acfc896833c79adc8463b765bb0 Mon Sep 17 00:00:00 2001 From: skyash-dev Date: Sat, 6 Jun 2026 23:18:12 +0530 Subject: [PATCH 2/2] refactor --- .../IDE/components/Editor/codemirror.js | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/client/modules/IDE/components/Editor/codemirror.js b/client/modules/IDE/components/Editor/codemirror.js index 1b72a17dae..1e44007f84 100644 --- a/client/modules/IDE/components/Editor/codemirror.js +++ b/client/modules/IDE/components/Editor/codemirror.js @@ -44,16 +44,11 @@ export default function useCodeMirror({ // The current codemirror files. const fileStates = useRef(); - // We have to create a ref for the file ID, or else the debouncer - // will old onto an old version of the fileId and just overrwrite the initial file. - const fileId = useRef(); - fileId.current = file.id; - - // CM6 update listeners are attached when file states are created and - // can outlive React renders. Keep dynamic values in a ref so onChange - // always sees the latest state. - const latestRef = useRef({}); - latestRef.current = { + // We store values used by the debounced onChange callback in a ref. + // Otherwise it can hold onto stale React state + const onChangeStateRef = useRef({}); + onChangeStateRef.current = { + fileId: file.id, autorefresh, project, files @@ -61,18 +56,18 @@ export default function useCodeMirror({ // When the file changes, update the file content and save status. function onChange() { - const latest = latestRef.current; + const state = onChangeStateRef.current; setUnsavedChanges(true); - updateFileContent(fileId.current, cmView.current.state.doc.toString()); + updateFileContent(state.fileId, cmView.current.state.doc.toString()); // Save a local backup to localStorage for crash recovery (#3891). // This ensures work is recoverable even if the tab crashes // (e.g. from an infinite loop) before the server autosave fires. - const projectId = latest.project?.id || 'unsaved'; - saveLocalBackup(projectId, latest.files); + const projectId = state.project?.id || 'unsaved'; + saveLocalBackup(projectId, state.files); - if (latest.autorefresh) { + if (state.autorefresh) { clearConsole(); startSketch(); }