From a1da9da0ef5970715cf989843e5dff2eb0cbe1fc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 12 Aug 2023 00:11:44 -0400 Subject: [PATCH] Avoid JSON parse error on playground load (#6519) ## Summary On page load, the playground very briefly flickers a JSON parse error. Due to our use of `useDeferredValue`, we attempt to parse the empty JSON string settings, since after `const initialized = ruffVersion != null;` returns true, we get one render with the stale deferred value. This PR refactors the state, such that we start by storing `null` for the `Source`, and use the `Source` itself to determine initialization status. ## Test Plan Set a breakpoint in the `catch` path in `Editor`; verified that it no longer triggers on load (but did on `main`). --- playground/src/Editor/Editor.tsx | 68 ++++++++++++++++---------------- playground/src/Editor/Header.tsx | 2 +- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/playground/src/Editor/Editor.tsx b/playground/src/Editor/Editor.tsx index a8b03bcfc6..79495084d3 100644 --- a/playground/src/Editor/Editor.tsx +++ b/playground/src/Editor/Editor.tsx @@ -5,22 +5,22 @@ import { useMemo, useState, } from "react"; +import { Panel, PanelGroup } from "react-resizable-panels"; import { DEFAULT_PYTHON_SOURCE } from "../constants"; import init, { Diagnostic, Workspace } from "../pkg"; import { ErrorMessage } from "./ErrorMessage"; import Header from "./Header"; -import { useTheme } from "./theme"; -import { persist, persistLocal, restore, stringify } from "./settings"; -import SettingsEditor from "./SettingsEditor"; -import SourceEditor from "./SourceEditor"; -import { Panel, PanelGroup } from "react-resizable-panels"; import PrimarySideBar from "./PrimarySideBar"; -import SecondarySideBar from "./SecondarySideBar"; import { HorizontalResizeHandle } from "./ResizeHandle"; import SecondaryPanel, { SecondaryPanelResult, SecondaryTool, } from "./SecondaryPanel"; +import SecondarySideBar from "./SecondarySideBar"; +import { persist, persistLocal, restore, stringify } from "./settings"; +import SettingsEditor from "./SettingsEditor"; +import SourceEditor from "./SourceEditor"; +import { useTheme } from "./theme"; type Tab = "Source" | "Settings"; @@ -43,11 +43,7 @@ export default function Editor() { error: null, secondary: null, }); - const [source, setSource] = useState({ - pythonSource: "", - settingsSource: "", - revision: 0, - }); + const [source, setSource] = useState(null); const [tab, setTab] = useState("Source"); const [secondaryTool, setSecondaryTool] = useState( @@ -64,8 +60,6 @@ export default function Editor() { ); const [theme, setTheme] = useTheme(); - const initialized = ruffVersion != null; - // Ideally this would be retrieved right from the URL... but routing without a proper // router is hard (there's no location changed event) and pulling in a router // feels overkill. @@ -97,8 +91,8 @@ export default function Editor() { ]; setSource({ - pythonSource, revision: 0, + pythonSource, settingsSource, }); setRuffVersion(Workspace.version()); @@ -109,11 +103,11 @@ export default function Editor() { const deferredSource = useDeferredValue(source); useEffect(() => { - if (!initialized) { + if (deferredSource == null) { return; } - const { settingsSource, pythonSource } = deferredSource; + const { pythonSource, settingsSource } = deferredSource; try { const config = JSON.parse(settingsSource); @@ -171,44 +165,52 @@ export default function Editor() { secondary: null, }); } - }, [initialized, deferredSource, secondaryTool]); + }, [deferredSource, secondaryTool]); useEffect(() => { - if (initialized) { + if (source != null) { persistLocal(source); } - }, [initialized, source]); + }, [source]); const handleShare = useMemo(() => { - if (!initialized) { + if (source == null) { return undefined; } return () => { return persist(source.settingsSource, source.pythonSource); }; - }, [source, initialized]); + }, [source]); const handlePythonSourceChange = useCallback((pythonSource: string) => { - setSource((state) => ({ - ...state, - pythonSource, - revision: state.revision + 1, - })); + setSource((state) => + state + ? { + ...state, + pythonSource, + revision: state.revision + 1, + } + : null, + ); }, []); const handleSettingsSourceChange = useCallback((settingsSource: string) => { - setSource((state) => ({ - ...state, - settingsSource, - revision: state.revision + 1, - })); + setSource((state) => + state + ? { + ...state, + settingsSource, + revision: state.revision + 1, + } + : null, + ); }, []); return (
- {initialized ? ( + {source ? ( setTab(tool)} diff --git a/playground/src/Editor/Header.tsx b/playground/src/Editor/Header.tsx index 636e1ae9e5..537f7d332f 100644 --- a/playground/src/Editor/Header.tsx +++ b/playground/src/Editor/Header.tsx @@ -14,7 +14,7 @@ export default function Header({ onChangeTheme, onShare, }: { - edit: number; + edit: number | null; theme: Theme; version: string | null; onChangeTheme: (theme: Theme) => void;