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

This commit is contained in:
David Luzar 2023-01-09 10:24:17 +01:00 committed by GitHub
parent 06b45e0cfc
commit 618442299f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 27 deletions

View File

@ -15,7 +15,11 @@ import {
BinaryFiles, BinaryFiles,
UIChildrenComponents, UIChildrenComponents,
} from "../types"; } from "../types";
import { muteFSAbortError, ReactChildrenToObject } from "../utils"; import {
isShallowEqual,
muteFSAbortError,
ReactChildrenToObject,
} from "../utils";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions"; import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import CollabButton from "./CollabButton"; import CollabButton from "./CollabButton";
import { ErrorDialog } from "./ErrorDialog"; import { ErrorDialog } from "./ErrorDialog";
@ -496,28 +500,39 @@ const LayerUI = ({
); );
}; };
const areEqual = (prev: LayerUIProps, next: LayerUIProps) => { const stripIrrelevantAppStateProps = (
const getNecessaryObj = (appState: AppState): Partial<AppState> => { appState: AppState,
const { ): Partial<AppState> => {
suggestedBindings, const { suggestedBindings, startBoundElement, cursorButton, ...ret } =
startBoundElement: boundElement, appState;
...ret return ret;
} = appState; };
return ret;
};
const prevAppState = getNecessaryObj(prev.appState);
const nextAppState = getNecessaryObj(next.appState);
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[]; 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 ( return (
prev.renderTopRightUI === next.renderTopRightUI && isShallowEqual(
prev.renderCustomStats === next.renderCustomStats && stripIrrelevantAppStateProps(prevAppState),
prev.renderCustomSidebar === next.renderCustomSidebar && stripIrrelevantAppStateProps(nextAppState),
prev.langCode === next.langCode && ) && isShallowEqual(prev, next)
prev.elements === next.elements &&
prev.files === next.files &&
keys.every((key) => prevAppState[key] === nextAppState[key])
); );
}; };

View File

@ -1,6 +1,7 @@
import React, { useEffect, forwardRef } from "react"; import React, { useEffect, forwardRef } from "react";
import { InitializeApp } from "../../components/InitializeApp"; import { InitializeApp } from "../../components/InitializeApp";
import App from "../../components/App"; import App from "../../components/App";
import { isShallowEqual } from "../../utils";
import "../../css/app.scss"; import "../../css/app.scss";
import "../../css/styles.scss"; import "../../css/styles.scss";
@ -128,6 +129,11 @@ const areEqual = (
prevProps: PublicExcalidrawProps, prevProps: PublicExcalidrawProps,
nextProps: PublicExcalidrawProps, nextProps: PublicExcalidrawProps,
) => { ) => {
// short-circuit early
if (prevProps.children !== nextProps.children) {
return false;
}
const { const {
initialData: prevInitialData, initialData: prevInitialData,
UIOptions: prevUIOptions = {}, UIOptions: prevUIOptions = {},
@ -176,13 +182,7 @@ const areEqual = (
return true; return true;
}); });
const prevKeys = Object.keys(prevProps) as (keyof typeof prev)[]; return isUIOptionsSame && isShallowEqual(prev, next);
const nextKeys = Object.keys(nextProps) as (keyof typeof next)[];
return (
isUIOptionsSame &&
prevKeys.length === nextKeys.length &&
prevKeys.every((key) => prev[key] === next[key])
);
}; };
const forwardedRefComp = forwardRef< const forwardedRefComp = forwardRef<

View File

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