mirror of https://github.com/astral-sh/ruff
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:
parent
c6ad364d8b
commit
a1da9da0ef
|
|
@ -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,
|
...state,
|
||||||
pythonSource,
|
pythonSource,
|
||||||
revision: state.revision + 1,
|
revision: state.revision + 1,
|
||||||
}));
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSettingsSourceChange = useCallback((settingsSource: string) => {
|
const handleSettingsSourceChange = useCallback((settingsSource: string) => {
|
||||||
setSource((state) => ({
|
setSource((state) =>
|
||||||
|
state
|
||||||
|
? {
|
||||||
...state,
|
...state,
|
||||||
settingsSource,
|
settingsSource,
|
||||||
revision: state.revision + 1,
|
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)}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue