perf: memoize rendering of library (#6622)

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Arnost Pleskot 2023-05-31 15:37:13 +02:00 committed by GitHub
parent 82d8d02697
commit 253c5c7866
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 377 additions and 306 deletions

View file

@ -1,4 +1,4 @@
import React, { useCallback } from "react";
import React, { useState, useCallback, useMemo, useRef } from "react";
import Library, {
distributeLibraryItemsOnSquareGrid,
libraryItemsAtom,
@ -27,6 +27,8 @@ import { useUIAppState } from "../context/ui-appState";
import "./LibraryMenu.scss";
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
import { isShallowEqual } from "../utils";
import { NonDeletedExcalidrawElement } from "../element/types";
export const isLibraryMenuOpenAtom = atom(false);
@ -42,7 +44,9 @@ export const LibraryMenuContent = ({
libraryReturnUrl,
library,
id,
appState,
theme,
selectedItems,
onSelectItems,
}: {
pendingElements: LibraryItem["elements"];
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
@ -51,33 +55,47 @@ export const LibraryMenuContent = ({
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
library: Library;
id: string;
appState: UIAppState;
theme: UIAppState["theme"];
selectedItems: LibraryItem["id"][];
onSelectItems: (id: LibraryItem["id"][]) => void;
}) => {
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
const addToLibrary = useCallback(
async (elements: LibraryItem["elements"], libraryItems: LibraryItems) => {
trackEvent("element", "addToLibrary", "ui");
if (elements.some((element) => element.type === "image")) {
return setAppState({
errorMessage: "Support for adding images to the library coming soon!",
const _onAddToLibrary = useCallback(
(elements: LibraryItem["elements"]) => {
const addToLibrary = async (
processedElements: LibraryItem["elements"],
libraryItems: LibraryItems,
) => {
trackEvent("element", "addToLibrary", "ui");
if (processedElements.some((element) => element.type === "image")) {
return setAppState({
errorMessage:
"Support for adding images to the library coming soon!",
});
}
const nextItems: LibraryItems = [
{
status: "unpublished",
elements: processedElements,
id: randomId(),
created: Date.now(),
},
...libraryItems,
];
onAddToLibrary();
library.setLibrary(nextItems).catch(() => {
setAppState({ errorMessage: t("alerts.errorAddingToLibrary") });
});
}
const nextItems: LibraryItems = [
{
status: "unpublished",
elements,
id: randomId(),
created: Date.now(),
},
...libraryItems,
];
onAddToLibrary();
library.setLibrary(nextItems).catch(() => {
setAppState({ errorMessage: t("alerts.errorAddingToLibrary") });
});
};
addToLibrary(elements, libraryItemsData.libraryItems);
},
[onAddToLibrary, library, setAppState],
[onAddToLibrary, library, setAppState, libraryItemsData.libraryItems],
);
const libraryItems = useMemo(
() => libraryItemsData.libraryItems,
[libraryItemsData],
);
if (
@ -103,15 +121,15 @@ export const LibraryMenuContent = ({
<LibraryMenuWrapper>
<LibraryMenuItems
isLoading={libraryItemsData.status === "loading"}
libraryItems={libraryItemsData.libraryItems}
onAddToLibrary={(elements) =>
addToLibrary(elements, libraryItemsData.libraryItems)
}
libraryItems={libraryItems}
onAddToLibrary={_onAddToLibrary}
onInsertLibraryItems={onInsertLibraryItems}
pendingElements={pendingElements}
id={id}
libraryReturnUrl={libraryReturnUrl}
theme={appState.theme}
theme={theme}
onSelectItems={onSelectItems}
selectedItems={selectedItems}
/>
{showBtn && (
<LibraryMenuControlButtons
@ -119,13 +137,36 @@ export const LibraryMenuContent = ({
style={{ padding: "16px 12px 0 12px" }}
id={id}
libraryReturnUrl={libraryReturnUrl}
theme={appState.theme}
theme={theme}
/>
)}
</LibraryMenuWrapper>
);
};
const usePendingElementsMemo = (
appState: UIAppState,
elements: readonly NonDeletedExcalidrawElement[],
) => {
const create = () => getSelectedElements(elements, appState, true);
const val = useRef(create());
const prevAppState = useRef<UIAppState>(appState);
const prevElements = useRef(elements);
if (
!isShallowEqual(
appState.selectedElementIds,
prevAppState.current.selectedElementIds,
) ||
!isShallowEqual(elements, prevElements.current)
) {
val.current = create();
prevAppState.current = appState;
prevElements.current = elements;
}
return val.current;
};
/**
* This component is meant to be rendered inside <Sidebar.Tab/> inside our
* <DefaultSidebar/> or host apps Sidebar components.
@ -136,9 +177,19 @@ export const LibraryMenu = () => {
const appState = useUIAppState();
const setAppState = useExcalidrawSetAppState();
const elements = useExcalidrawElements();
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
const memoizedLibrary = useMemo(() => library, [library]);
// BUG: pendingElements are still causing some unnecessary rerenders because clicking into canvas returns some ids even when no element is selected.
const pendingElements = usePendingElementsMemo(appState, elements);
const onAddToLibrary = useCallback(() => {
// deselect canvas elements
const onInsertLibraryItems = useCallback(
(libraryItems: LibraryItems) => {
onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
},
[onInsertElements],
);
const deselectItems = useCallback(() => {
setAppState({
selectedElementIds: {},
selectedGroupIds: {},
@ -147,16 +198,16 @@ export const LibraryMenu = () => {
return (
<LibraryMenuContent
pendingElements={getSelectedElements(elements, appState, true)}
onInsertLibraryItems={(libraryItems) => {
onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
}}
onAddToLibrary={onAddToLibrary}
pendingElements={pendingElements}
onInsertLibraryItems={onInsertLibraryItems}
onAddToLibrary={deselectItems}
setAppState={setAppState}
libraryReturnUrl={appProps.libraryReturnUrl}
library={library}
library={memoizedLibrary}
id={id}
appState={appState}
theme={appState.theme}
selectedItems={selectedItems}
onSelectItems={setSelectedItems}
/>
);
};