diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..4cb965d --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ +## 2023-10-27 - Excalidraw Component Re-render Performance Penalty + +**Learning:** Due to the exceptionally high rendering cost of the `@excalidraw/excalidraw` canvas, inline functions passed as props (like `onChange` and `onExcalidrawAPI`) cause severe performance degradation by defeating `React.memo` and triggering full canvas re-renders when parent state changes. +**Action:** Always strictly memoize all callbacks passed to the Excalidraw component using `useCallback` to preserve prop stability and prevent unnecessary, expensive re-renders. diff --git a/src/client/App.tsx b/src/client/App.tsx index 33ddb41..5fd06e8 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -307,6 +307,10 @@ function PrivateApp() { [], ); + const handleExcalidrawAPI = useCallback((api: ExcalidrawImperativeAPI) => { + excalidrawApiRef.current = api; + }, []); + return (
{toastMessage && ( @@ -332,9 +336,7 @@ function PrivateApp() { onSceneChange={handleSceneChange} onSelectionStateChange={handleCodeBlockSelectionChange} onEditorActivity={scheduleThemeTokenSync} - onExcalidrawAPI={(api) => { - excalidrawApiRef.current = api; - }} + onExcalidrawAPI={handleExcalidrawAPI} renderEmbeddable={renderCodeBlockEmbeddable} /> { + onEditorActivity(); + onSceneChange(sceneFromEditor(elements, appState, files)); + onSelectionStateChange(getCodeBlockSelectionState(elements, appState)); + }, + [onEditorActivity, onSceneChange, onSelectionStateChange], + ); + if (loading || !scene) { return
{error ?? "Loading..."}
; } @@ -67,13 +81,7 @@ export const EditorCanvas = memo(function EditorCanvas({ initialData={sceneToInitialData(scene)} excalidrawAPI={onExcalidrawAPI} renderEmbeddable={renderEmbeddable} - onChange={(elements, appState, files) => { - onEditorActivity(); - onSceneChange(sceneFromEditor(elements, appState, files)); - onSelectionStateChange( - getCodeBlockSelectionState(elements, appState), - ); - }} + onChange={handleChange} />
);