From 80e53ef03c58cf98df82fb4abcfc5ac6ca5944ae Mon Sep 17 00:00:00 2001 From: ruinivist <179396038+ruinivist@users.noreply.github.com> Date: Sun, 7 Jun 2026 07:41:04 +0000 Subject: [PATCH] perf: memoize Excalidraw API callback to prevent full canvas re-renders This optimization prevents the `EditorCanvas` (and thereby the heavy `@excalidraw/excalidraw` component) from re-rendering due to prop instability of the inline `onExcalidrawAPI` function in `PrivateApp` when parent state changes. This mitigates severe UI input lag. A new entry was also added to `.jules/bolt.md` documenting this insight. --- .jules/bolt.md | 4 ++++ src/client/App.tsx | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..d9b922a --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ +## 2024-06-07 - Memoize callbacks passed to Excalidraw + +**Learning:** Due to the exceptionally high rendering cost of the `@excalidraw/excalidraw` canvas, always ensure all callbacks passed to it (e.g., `onExcalidrawAPI`) are strictly memoized using `useCallback`. Passing inline functions as props breaks prop stability for `React.memo` and causes severe UI input lag caused by full canvas re-renders when parent states change. +**Action:** When working with `` or its wrapper components like ``, always extract inline callback functions (like `onExcalidrawAPI={(api) => { ... }}`) into `useCallback` hooks before passing them down. diff --git a/src/client/App.tsx b/src/client/App.tsx index 33ddb41..40151b2 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -307,6 +307,13 @@ function PrivateApp() { [], ); + // ⚡ Bolt: Memoize the Excalidraw API callback to prevent EditorCanvas from re-rendering + // Excalidraw has exceptionally high rendering cost, so we must maintain prop stability + // for EditorCanvas's React.memo to avoid severe UI input lag on any parent state change. + const handleExcalidrawAPI = useCallback((api: ExcalidrawImperativeAPI) => { + excalidrawApiRef.current = api; + }, []); + return (
{toastMessage && ( @@ -332,9 +339,7 @@ function PrivateApp() { onSceneChange={handleSceneChange} onSelectionStateChange={handleCodeBlockSelectionChange} onEditorActivity={scheduleThemeTokenSync} - onExcalidrawAPI={(api) => { - excalidrawApiRef.current = api; - }} + onExcalidrawAPI={handleExcalidrawAPI} renderEmbeddable={renderCodeBlockEmbeddable} />