diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index e8a385d0..47f34457 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -15,7 +15,11 @@ import { BinaryFiles, UIChildrenComponents, } from "../types"; -import { muteFSAbortError, ReactChildrenToObject } from "../utils"; +import { + isShallowEqual, + muteFSAbortError, + ReactChildrenToObject, +} from "../utils"; import { SelectedShapeActions, ShapesSwitcher } from "./Actions"; import CollabButton from "./CollabButton"; import { ErrorDialog } from "./ErrorDialog"; @@ -496,28 +500,39 @@ const LayerUI = ({ ); }; -const areEqual = (prev: LayerUIProps, next: LayerUIProps) => { - const getNecessaryObj = (appState: AppState): Partial => { - const { - suggestedBindings, - startBoundElement: boundElement, - ...ret - } = appState; - return ret; - }; - const prevAppState = getNecessaryObj(prev.appState); - const nextAppState = getNecessaryObj(next.appState); +const stripIrrelevantAppStateProps = ( + appState: AppState, +): Partial => { + const { suggestedBindings, startBoundElement, cursorButton, ...ret } = + appState; + return ret; +}; - const keys = Object.keys(prevAppState) as (keyof Partial)[]; +const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => { + // short-circuit early + if (prevProps.children !== nextProps.children) { + return false; + } + + const { + canvas: _prevCanvas, + // not stable, but shouldn't matter in our case + onInsertElements: _prevOnInsertElements, + appState: prevAppState, + ...prev + } = prevProps; + const { + canvas: _nextCanvas, + onInsertElements: _nextOnInsertElements, + appState: nextAppState, + ...next + } = nextProps; return ( - prev.renderTopRightUI === next.renderTopRightUI && - prev.renderCustomStats === next.renderCustomStats && - prev.renderCustomSidebar === next.renderCustomSidebar && - prev.langCode === next.langCode && - prev.elements === next.elements && - prev.files === next.files && - keys.every((key) => prevAppState[key] === nextAppState[key]) + isShallowEqual( + stripIrrelevantAppStateProps(prevAppState), + stripIrrelevantAppStateProps(nextAppState), + ) && isShallowEqual(prev, next) ); }; diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index 3f125fe9..4dc1f98b 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -1,6 +1,7 @@ import React, { useEffect, forwardRef } from "react"; import { InitializeApp } from "../../components/InitializeApp"; import App from "../../components/App"; +import { isShallowEqual } from "../../utils"; import "../../css/app.scss"; import "../../css/styles.scss"; @@ -128,6 +129,11 @@ const areEqual = ( prevProps: PublicExcalidrawProps, nextProps: PublicExcalidrawProps, ) => { + // short-circuit early + if (prevProps.children !== nextProps.children) { + return false; + } + const { initialData: prevInitialData, UIOptions: prevUIOptions = {}, @@ -176,13 +182,7 @@ const areEqual = ( return true; }); - const prevKeys = Object.keys(prevProps) as (keyof typeof prev)[]; - const nextKeys = Object.keys(nextProps) as (keyof typeof next)[]; - return ( - isUIOptionsSame && - prevKeys.length === nextKeys.length && - prevKeys.every((key) => prev[key] === next[key]) - ); + return isUIOptionsSame && isShallowEqual(prev, next); }; const forwardedRefComp = forwardRef< diff --git a/src/utils.ts b/src/utils.ts index 119eb7ae..6f8010d9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -709,3 +709,15 @@ export const ReactChildrenToObject = < return acc; }, {} as Partial); }; + +export const isShallowEqual = >( + objA: T, + objB: T, +) => { + const aKeys = Object.keys(objA); + const bKeys = Object.keys(objA); + if (aKeys.length !== bKeys.length) { + return false; + } + return aKeys.every((key) => objA[key] === objB[key]); +};