Repository: astral-sh/ruff
File: playground/ty/src/Playground.tsx (TypeScript)

Code before:
  useReducer,
  useState,
} from "react";
import { ErrorMessage, Header, setupMonaco, useTheme } from "shared";
import { FileHandle, PositionEncoding, Workspace } from "ty_wasm";
import { persist, persistLocal, restore } from "./Editor/persist";
import { loader } from "@monaco-editor/react";
import tySchema from "../../../ty.schema.json";
import Chrome, { formatError } from "./Editor/Chrome";

let _cachedPlayground: Promise<InitializedPlayground> | null = null;
let workspaceInitComplete = false;

export const SETTINGS_FILE_NAME = "ty.json";

export default function Playground() {
  const [theme, setTheme] = useTheme();
  const [version, setVersion] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [workspace, setWorkspace] = useState<Workspace | null>(null);
  const [files, dispatchFiles] = useReducer(filesReducer, INIT_FILES_STATE);

  const [workspacePromise] = useState<Promise<Workspace>>(() => {
    return startPlayground().then((fetched) => {
      const workspace = new Workspace("/", PositionEncoding.Utf16, {});
      if (!workspaceInitComplete) {
        setVersion(fetched.version);
        restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
        setWorkspace(workspace);
        workspaceInitComplete = true;
      }
      return workspace;
    });
  });

  const fileName = useMemo(() => {
    return (
      files.index.find((file) => file.id === files.selected)?.name ?? "lib.py"
    );
  }, [files.index, files.selected]);

  usePersistLocally(files);

  const handleShare = useCallback(async () => {
    const serialized = serializeFiles(files);

    if (serialized != null) {
      await persist(serialized);
    }
  }, [files]);


Review comment (suggestion):
Can't say that I like this but not running `restoreWorkspace` multiple times seems to be what's actually important to not get the weird behaviour and I'm not sure if there's a much better way

Code after:
  useReducer,
  useRef,
  useState,
} from "react";
import { ErrorMessage, Header, setupMonaco, useTheme } from "shared";
import { FileHandle, PositionEncoding, Workspace } from "ty_wasm";
import { persist, persistLocal, restore } from "./Editor/persist";
import { loader } from "@monaco-editor/react";
import tySchema from "../../../ty.schema.json";
import Chrome, { formatError } from "./Editor/Chrome";

export const SETTINGS_FILE_NAME = "ty.json";

export default function Playground() {
  const [theme, setTheme] = useTheme();
  const [version, setVersion] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [workspace, setWorkspace] = useState<Workspace | null>(null);
  const [files, dispatchFiles] = useReducer(filesReducer, INIT_FILES_STATE);

  const workspacePromiseRef = useRef<Promise<Workspace> | null>(null);
  if (workspacePromiseRef.current == null) {
    workspacePromiseRef.current = startPlayground().then((fetched) => {
      setVersion(fetched.version);
      const workspace = new Workspace("/", PositionEncoding.Utf16, {});
      restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
      setWorkspace(workspace);
      return workspace;
    });
  }
  // This is safe as this is only called once on startup.
  // We need useRef to avoid duplicate initialization when
  // running locally due to react rendering
  // everything twice in strict mode in debug builds.
  // eslint-disable-next-line react-hooks/refs
  const workspacePromise = workspacePromiseRef.current;

  const fileName = useMemo(() => {
    return (
      files.index.find((file) => file.id === files.selected)?.name ?? "lib.py"
    );
  }, [files.index, files.selected]);

  usePersistLocally(files);

  const handleShare = useCallback(async () => {
    const serialized = serializeFiles(files);

    if (serialized != null) {
      await persist(serialized);
    }