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`).
This commit is contained in:
Charlie Marsh 2023-08-12 00:11:44 -04:00 committed by GitHub
parent c6ad364d8b
commit a1da9da0ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 34 deletions

View File

@ -5,22 +5,22 @@ import {
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import { Panel, PanelGroup } from "react-resizable-panels";
import { DEFAULT_PYTHON_SOURCE } from "../constants"; import { DEFAULT_PYTHON_SOURCE } from "../constants";
import init, { Diagnostic, Workspace } from "../pkg"; import init, { Diagnostic, Workspace } from "../pkg";
import { ErrorMessage } from "./ErrorMessage"; import { ErrorMessage } from "./ErrorMessage";
import Header from "./Header"; 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 PrimarySideBar from "./PrimarySideBar";
import SecondarySideBar from "./SecondarySideBar";
import { HorizontalResizeHandle } from "./ResizeHandle"; import { HorizontalResizeHandle } from "./ResizeHandle";
import SecondaryPanel, { import SecondaryPanel, {
SecondaryPanelResult, SecondaryPanelResult,
SecondaryTool, SecondaryTool,
} from "./SecondaryPanel"; } 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"; type Tab = "Source" | "Settings";
@ -43,11 +43,7 @@ export default function Editor() {
error: null, error: null,
secondary: null, secondary: null,
}); });
const [source, setSource] = useState<Source>({ const [source, setSource] = useState<Source | null>(null);
pythonSource: "",
settingsSource: "",
revision: 0,
});
const [tab, setTab] = useState<Tab>("Source"); const [tab, setTab] = useState<Tab>("Source");
const [secondaryTool, setSecondaryTool] = useState<SecondaryTool | null>( const [secondaryTool, setSecondaryTool] = useState<SecondaryTool | null>(
@ -64,8 +60,6 @@ export default function Editor() {
); );
const [theme, setTheme] = useTheme(); const [theme, setTheme] = useTheme();
const initialized = ruffVersion != null;
// Ideally this would be retrieved right from the URL... but routing without a proper // 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 // router is hard (there's no location changed event) and pulling in a router
// feels overkill. // feels overkill.
@ -97,8 +91,8 @@ export default function Editor() {
]; ];
setSource({ setSource({
pythonSource,
revision: 0, revision: 0,
pythonSource,
settingsSource, settingsSource,
}); });
setRuffVersion(Workspace.version()); setRuffVersion(Workspace.version());
@ -109,11 +103,11 @@ export default function Editor() {
const deferredSource = useDeferredValue(source); const deferredSource = useDeferredValue(source);
useEffect(() => { useEffect(() => {
if (!initialized) { if (deferredSource == null) {
return; return;
} }
const { settingsSource, pythonSource } = deferredSource; const { pythonSource, settingsSource } = deferredSource;
try { try {
const config = JSON.parse(settingsSource); const config = JSON.parse(settingsSource);
@ -171,44 +165,52 @@ export default function Editor() {
secondary: null, secondary: null,
}); });
} }
}, [initialized, deferredSource, secondaryTool]); }, [deferredSource, secondaryTool]);
useEffect(() => { useEffect(() => {
if (initialized) { if (source != null) {
persistLocal(source); persistLocal(source);
} }
}, [initialized, source]); }, [source]);
const handleShare = useMemo(() => { const handleShare = useMemo(() => {
if (!initialized) { if (source == null) {
return undefined; return undefined;
} }
return () => { return () => {
return persist(source.settingsSource, source.pythonSource); return persist(source.settingsSource, source.pythonSource);
}; };
}, [source, initialized]); }, [source]);
const handlePythonSourceChange = useCallback((pythonSource: string) => { const handlePythonSourceChange = useCallback((pythonSource: string) => {
setSource((state) => ({ setSource((state) =>
...state, state
pythonSource, ? {
revision: state.revision + 1, ...state,
})); pythonSource,
revision: state.revision + 1,
}
: null,
);
}, []); }, []);
const handleSettingsSourceChange = useCallback((settingsSource: string) => { const handleSettingsSourceChange = useCallback((settingsSource: string) => {
setSource((state) => ({ setSource((state) =>
...state, state
settingsSource, ? {
revision: state.revision + 1, ...state,
})); settingsSource,
revision: state.revision + 1,
}
: null,
);
}, []); }, []);
return ( return (
<main className="flex flex-col h-full bg-ayu-background dark:bg-ayu-background-dark"> <main className="flex flex-col h-full bg-ayu-background dark:bg-ayu-background-dark">
<Header <Header
edit={source.revision} edit={source ? source.revision : null}
theme={theme} theme={theme}
version={ruffVersion} version={ruffVersion}
onChangeTheme={setTheme} onChangeTheme={setTheme}
@ -216,7 +218,7 @@ export default function Editor() {
/> />
<div className="flex flex-grow"> <div className="flex flex-grow">
{initialized ? ( {source ? (
<PanelGroup direction="horizontal" autoSaveId="main"> <PanelGroup direction="horizontal" autoSaveId="main">
<PrimarySideBar <PrimarySideBar
onSelectTool={(tool) => setTab(tool)} onSelectTool={(tool) => setTab(tool)}

View File

@ -14,7 +14,7 @@ export default function Header({
onChangeTheme, onChangeTheme,
onShare, onShare,
}: { }: {
edit: number; edit: number | null;
theme: Theme; theme: Theme;
version: string | null; version: string | null;
onChangeTheme: (theme: Theme) => void; onChangeTheme: (theme: Theme) => void;