Forráskód Böngészése

fix: React.memo resolvers not accounting for all props (#6042)

David Luzar 2 éve
szülő
commit
618442299f
3 módosított fájl, 54 hozzáadás és 27 törlés
  1. 35 20
      src/components/LayerUI.tsx
  2. 7 7
      src/packages/excalidraw/index.tsx
  3. 12 0
      src/utils.ts

+ 35 - 20
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<AppState> => {
-    const {
-      suggestedBindings,
-      startBoundElement: boundElement,
-      ...ret
-    } = appState;
-    return ret;
-  };
-  const prevAppState = getNecessaryObj(prev.appState);
-  const nextAppState = getNecessaryObj(next.appState);
+const stripIrrelevantAppStateProps = (
+  appState: AppState,
+): Partial<AppState> => {
+  const { suggestedBindings, startBoundElement, cursorButton, ...ret } =
+    appState;
+  return ret;
+};
+
+const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => {
+  // short-circuit early
+  if (prevProps.children !== nextProps.children) {
+    return false;
+  }
 
-  const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
+  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)
   );
 };
 

+ 7 - 7
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<

+ 12 - 0
src/utils.ts

@@ -709,3 +709,15 @@ export const ReactChildrenToObject = <
     return acc;
   }, {} as Partial<T>);
 };
+
+export const isShallowEqual = <T extends Record<string, any>>(
+  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]);
+};