From 2c2a348a2ffdb9ce6f6ac27566a7080511c76467 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Tue, 15 Apr 2025 11:20:43 -0500 Subject: [PATCH] refactor: reduce menu bar rerenders (#7589) * reduce menu bar rerenders * imports cleanup * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../components/FlowMenu/index.tsx | 80 ++++++++++++------- src/frontend/src/hooks/useUnsavedChanges.ts | 18 +++++ .../src/modals/flowSettingsModal/index.tsx | 6 +- 3 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 src/frontend/src/hooks/useUnsavedChanges.ts diff --git a/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx b/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx index 6e0af694f..37834df7f 100644 --- a/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx +++ b/src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx @@ -4,7 +4,6 @@ import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import useAddFlow from "@/hooks/flows/use-add-flow"; import useSaveFlow from "@/hooks/flows/use-save-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow"; -import { customStringify } from "@/utils/reactflowUtils"; import { useHotkeys } from "react-hotkeys-hook"; import IconComponent from "@/components/common/genericIconComponent"; @@ -22,6 +21,7 @@ import { UPLOAD_ERROR_ALERT } from "@/constants/alerts_constants"; import { SAVED_HOVER } from "@/constants/constants"; import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query"; import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders"; +import { useUnsavedChanges } from "@/hooks/useUnsavedChanges"; import ExportModal from "@/modals/exportModal"; import FlowLogsModal from "@/modals/flowLogsModal"; import FlowSettingsModal from "@/modals/flowSettingsModal"; @@ -33,6 +33,7 @@ import { useShortcutsStore } from "@/stores/shortcuts"; import { swatchColors } from "@/utils/styleUtils"; import { cn, getNumberFromString } from "@/utils/utils"; import { useQueryClient } from "@tanstack/react-query"; +import { useShallow } from "zustand/react/shallow"; export const MenuBar = ({}: {}): JSX.Element => { const shortcuts = useShortcutsStore((state) => state.shortcuts); @@ -50,18 +51,36 @@ export const MenuBar = ({}: {}): JSX.Element => { const saveFlow = useSaveFlow(); const queryClient = useQueryClient(); const autoSaving = useFlowsManagerStore((state) => state.autoSaving); - const currentFlow = useFlowStore((state) => state.currentFlow); - const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow); - const updatedAt = currentSavedFlow?.updated_at; + const { + currentFlowName, + currentFlowId, + currentFlowFolderId, + currentFlowIcon, + currentFlowGradient, + } = useFlowStore( + useShallow((state) => ({ + currentFlowName: state.currentFlow?.name, + currentFlowId: state.currentFlow?.id, + currentFlowFolderId: state.currentFlow?.folder_id, + currentFlowIcon: state.currentFlow?.icon, + currentFlowGradient: state.currentFlow?.gradient, + })), + ); + const { updated_at: updatedAt } = useFlowsManagerStore( + useShallow((state) => ({ + updated_at: state.currentFlow?.updated_at, + })), + ); const onFlowPage = useFlowStore((state) => state.onFlowPage); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const stopBuilding = useFlowStore((state) => state.stopBuilding); const [editingName, setEditingName] = useState(false); - const [flowName, setFlowName] = useState(currentFlow?.name ?? ""); + const [flowName, setFlowName] = useState(currentFlowName ?? ""); const [isInvalidName, setIsInvalidName] = useState(false); const nameInputRef = useRef(null); const [inputWidth, setInputWidth] = useState(0); const measureRef = useRef(null); + const changesNotSaved = useUnsavedChanges(); const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery(); const flows = useFlowsManagerStore((state) => state.flows); @@ -73,9 +92,9 @@ export const MenuBar = ({}: {}): JSX.Element => { flows.forEach((flow) => { tempNameList.push(flow.name); }); - setNameList(tempNameList.filter((name) => name !== currentFlow?.name)); + setNameList(tempNameList.filter((name) => name !== currentFlowName)); } - }, [flows, currentFlow?.name]); + }, [flows, currentFlowName]); useGetRefreshFlowsQuery( { @@ -86,13 +105,10 @@ export const MenuBar = ({}: {}): JSX.Element => { ); const currentFolder = useMemo( - () => folders?.find((f) => f.id === currentFlow?.folder_id), - [folders, currentFlow?.folder_id], + () => folders?.find((f) => f.id === currentFlowFolderId), + [folders, currentFlowFolderId], ); - const changesNotSaved = - customStringify(currentFlow) !== customStringify(currentSavedFlow); - useEffect(() => { if (measureRef.current) { setInputWidth(measureRef.current.offsetWidth); @@ -163,26 +179,29 @@ export const MenuBar = ({}: {}): JSX.Element => { (e: React.KeyboardEvent) => { if (e.key === "Escape") { setEditingName(false); - setFlowName(currentFlow?.name ?? ""); + setFlowName(currentFlowName ?? ""); setIsInvalidName(false); } if (e.key === "Enter") { nameInputRef.current?.blur(); } }, - [currentFlow?.name], + [currentFlowName], ); const handleNameSubmit = useCallback(() => { if ( flowName.trim() !== "" && - flowName !== currentFlow?.name && + flowName !== currentFlowName && !isInvalidName ) { + // Get a one-time snapshot of currentFlow using get() + const currentFlowSnapshot = useFlowStore.getState().currentFlow; + const newFlow = { - ...currentFlow!, + ...currentFlowSnapshot!, name: flowName, - id: currentFlow!.id, + id: currentFlowId!, }; setCurrentFlow(newFlow); saveFlow(newFlow) @@ -194,22 +213,23 @@ export const MenuBar = ({}: {}): JSX.Element => { title: "Error updating flow name", list: [(error as Error).message], }); - setFlowName(currentFlow?.name ?? ""); + setFlowName(currentFlowName ?? ""); }); } else if (isInvalidName) { setErrorData({ title: "Invalid flow name", list: ["Name already exists"], }); - setFlowName(currentFlow?.name ?? ""); + setFlowName(currentFlowName ?? ""); } else { - setFlowName(currentFlow?.name ?? ""); + setFlowName(currentFlowName ?? ""); } setEditingName(false); setIsInvalidName(false); }, [ flowName, - currentFlow, + currentFlowName, + currentFlowId, setCurrentFlow, saveFlow, setSuccessData, @@ -218,10 +238,10 @@ export const MenuBar = ({}: {}): JSX.Element => { ]); useEffect(() => { - if (currentFlow && !editingName) { - setFlowName(currentFlow.name); + if (currentFlowName && !editingName) { + setFlowName(currentFlowName); } - }, [currentFlow, editingName]); + }, [currentFlowName, editingName]); useEffect(() => { if (measureRef.current) { @@ -230,12 +250,12 @@ export const MenuBar = ({}: {}): JSX.Element => { }, [flowName]); const swatchIndex = - (currentFlow?.gradient && !isNaN(parseInt(currentFlow?.gradient)) - ? parseInt(currentFlow?.gradient) - : getNumberFromString(currentFlow?.gradient ?? currentFlow?.id ?? "")) % + (currentFlowGradient && !isNaN(parseInt(currentFlowGradient)) + ? parseInt(currentFlowGradient) + : getNumberFromString(currentFlowGradient ?? currentFlowId ?? "")) % swatchColors.length; - return currentFlow && onFlowPage ? ( + return currentFlowName && onFlowPage ? (
{
@@ -305,7 +325,7 @@ export const MenuBar = ({}: {}): JSX.Element => { onKeyDown={handleKeyDown} onFocus={() => { setEditingName(true); - setFlowName(currentFlow.name); + setFlowName(currentFlowName); }} onBlur={handleNameSubmit} value={flowName} diff --git a/src/frontend/src/hooks/useUnsavedChanges.ts b/src/frontend/src/hooks/useUnsavedChanges.ts new file mode 100644 index 000000000..ab71192a7 --- /dev/null +++ b/src/frontend/src/hooks/useUnsavedChanges.ts @@ -0,0 +1,18 @@ +import useFlowStore from "../stores/flowStore"; +import useFlowsManagerStore from "../stores/flowsManagerStore"; +import { customStringify } from "../utils/reactflowUtils"; + +export function useUnsavedChanges() { + const currentFlow = useFlowStore((state) => state.currentFlow); + const savedFlow = useFlowsManagerStore((state) => state.currentFlow); + + if (!currentFlow || !savedFlow) { + return false; + } + + if ((currentFlow?.data?.nodes?.length ?? 0) > 0) { + return false; + } + + return customStringify(currentFlow) !== customStringify(savedFlow); +} diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index 8e277455c..753df57fb 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -18,8 +18,12 @@ export default function FlowSettingsModal({ flowData, details, }: FlowSettingsPropsType): JSX.Element { + if (!open) return <>; + const saveFlow = useSaveFlow(); - const currentFlow = useFlowStore((state) => state.currentFlow); + const currentFlow = useFlowStore((state) => + flowData ? undefined : state.currentFlow, + ); const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow); const setSuccessData = useAlertStore((state) => state.setSuccessData); const flows = useFlowsManagerStore((state) => state.flows);