fix: React.memo resolvers not accounting for all props (#6042)
This commit is contained in:
parent
06b45e0cfc
commit
618442299f
@ -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])
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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<
|
||||||
|
12
src/utils.ts
12
src/utils.ts
@ -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]);
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user