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,
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<Source>({
pythonSource: "",
settingsSource: "",
revision: 0,
});
const [source, setSource] = useState<Source | null>(null);
const [tab, setTab] = useState<Tab>("Source");
const [secondaryTool, setSecondaryTool] = useState<SecondaryTool | null>(
@ -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) => ({
setSource((state) =>
state
? {
...state,
pythonSource,
revision: state.revision + 1,
}));
}
: null,
);
}, []);
const handleSettingsSourceChange = useCallback((settingsSource: string) => {
setSource((state) => ({
setSource((state) =>
state
? {
...state,
settingsSource,
revision: state.revision + 1,
}));
}
: null,
);
}, []);
return (
<main className="flex flex-col h-full bg-ayu-background dark:bg-ayu-background-dark">
<Header
edit={source.revision}
edit={source ? source.revision : null}
theme={theme}
version={ruffVersion}
onChangeTheme={setTheme}
@ -216,7 +218,7 @@ export default function Editor() {
/>
<div className="flex flex-grow">
{initialized ? (
{source ? (
<PanelGroup direction="horizontal" autoSaveId="main">
<PrimarySideBar
onSelectTool={(tool) => setTab(tool)}

View File

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