excalidraw / excalidraw

Virtual whiteboard for sketching hand-drawn like diagrams
https://excalidraw.com
MIT License
84.47k stars 7.96k forks source link

Elements set via updateScene() just after getting the API is overwritten to initialData #7585

Open mikecat opened 9 months ago

mikecat commented 9 months ago

Elements set via updateScene() just after getting the API is overwritten to initialData.

Example code:

"use client";

import { useState, useEffect, useCallback } from "react";

//import { Excalidraw } from "../../../excalidraw/packages/excalidraw/index";
import { Excalidraw } from "@excalidraw/excalidraw";

const element = {
  "id":"aqh9uovTnRdmHy-NZ8dG-",
  "type":"rectangle",
  "x":515,
  "y":201,
  "width":273,
  "height":171,
  "angle":0,
  "strokeColor":"#1e1e1e",
  "backgroundColor":"transparent",
  "fillStyle":"solid",
  "strokeWidth":2,
  "strokeStyle":"solid",
  "roughness":1,
  "opacity":100,
  "groupIds":[],
  "frameId":null,
  "roundness":{"type":3},
  "seed":335480479,
  "version":24,
  "versionNonce":574038399,
  "isDeleted":false,
  "boundElements":null,
  "updated":1705661796295,
  "link":null,
  "locked":false
};

export default function App() {
  const [api, setAPI] = useState<any>();

  useEffect(() => {
    if (api) {
      console.log(api.getSceneElementsIncludingDeleted());
      api.updateScene({
        elements: [element],
      });
      console.log(api.getSceneElementsIncludingDeleted());
    }
  }, [api]);

  const onCheck = useCallback(() => {
    if (api) {
      alert(JSON.stringify(api.getSceneElementsIncludingDeleted()));
    }
  }, [api]);

  const onSet = useCallback(() => {
    if (api) {
      api.updateScene({
        elements: [element],
      });
    }
  }, [api]);

  return (
    <div style={{height: "100%", display: "grid", gridTemplateRows: "auto 1fr"}}>
      <div>
        <button type="button" onClick={onCheck}>check</button>
        <button type="button" onClick={onSet}>set</button>
      </div>
      <div>
        <Excalidraw excalidrawAPI={setAPI} />
      </div>
    </div>
  );
}

On opening this page, I expect to see an rectange in the canvas.
However, I actually get a blank canvas.

After some investigation, I found these events are happening:

  1. initializeScene (in packages/excalidraw/components/App.tsx) is called from componentDidMount. Elements in the scene is set t according to initialData (blank in this example).
  2. The scene is set to have one rectangle via the updateScene API from the function passed to useEffect.
  3. componentDidMount is called again. It calls initializeScene and set the scene to blank again.

I think there should be some guard or something to prevent the 2nd call of initializeScene from initializing things (elements, appState, etc.) that are already initialized/set to fix this issue.

tibetsprague commented 9 months ago

I'm experiencing the same thing