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>
This commit is contained in:
Mike Fortman 2025-04-15 11:20:43 -05:00 • committed by GitHub
commit 2c2a348a2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 31 deletions

View file

@ -4,7 +4,6 @@ import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow"; import useAddFlow from "@/hooks/flows/use-add-flow";
import useSaveFlow from "@/hooks/flows/use-save-flow"; import useSaveFlow from "@/hooks/flows/use-save-flow";
import useUploadFlow from "@/hooks/flows/use-upload-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { customStringify } from "@/utils/reactflowUtils";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import IconComponent from "@/components/common/genericIconComponent"; 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 { SAVED_HOVER } from "@/constants/constants";
import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query"; import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query";
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders"; import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
import { useUnsavedChanges } from "@/hooks/useUnsavedChanges";
import ExportModal from "@/modals/exportModal"; import ExportModal from "@/modals/exportModal";
import FlowLogsModal from "@/modals/flowLogsModal"; import FlowLogsModal from "@/modals/flowLogsModal";
import FlowSettingsModal from "@/modals/flowSettingsModal"; import FlowSettingsModal from "@/modals/flowSettingsModal";
@ -33,6 +33,7 @@ import { useShortcutsStore } from "@/stores/shortcuts";
import { swatchColors } from "@/utils/styleUtils"; import { swatchColors } from "@/utils/styleUtils";
import { cn, getNumberFromString } from "@/utils/utils"; import { cn, getNumberFromString } from "@/utils/utils";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useShallow } from "zustand/react/shallow";
export const MenuBar = ({}: {}): JSX.Element => { export const MenuBar = ({}: {}): JSX.Element => {
const shortcuts = useShortcutsStore((state) => state.shortcuts); const shortcuts = useShortcutsStore((state) => state.shortcuts);
@ -50,18 +51,36 @@ export const MenuBar = ({}: {}): JSX.Element => {
const saveFlow = useSaveFlow(); const saveFlow = useSaveFlow();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const autoSaving = useFlowsManagerStore((state) => state.autoSaving); const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
const currentFlow = useFlowStore((state) => state.currentFlow); const {
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow); currentFlowName,
const updatedAt = currentSavedFlow?.updated_at; 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 onFlowPage = useFlowStore((state) => state.onFlowPage);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const stopBuilding = useFlowStore((state) => state.stopBuilding); const stopBuilding = useFlowStore((state) => state.stopBuilding);
const [editingName, setEditingName] = useState(false); const [editingName, setEditingName] = useState(false);
const [flowName, setFlowName] = useState(currentFlow?.name ?? ""); const [flowName, setFlowName] = useState(currentFlowName ?? "");
const [isInvalidName, setIsInvalidName] = useState(false); const [isInvalidName, setIsInvalidName] = useState(false);
const nameInputRef = useRef<HTMLInputElement>(null); const nameInputRef = useRef<HTMLInputElement>(null);
const [inputWidth, setInputWidth] = useState<number>(0); const [inputWidth, setInputWidth] = useState<number>(0);
const measureRef = useRef<HTMLSpanElement>(null); const measureRef = useRef<HTMLSpanElement>(null);
const changesNotSaved = useUnsavedChanges();
const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery(); const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery();
const flows = useFlowsManagerStore((state) => state.flows); const flows = useFlowsManagerStore((state) => state.flows);
@ -73,9 +92,9 @@ export const MenuBar = ({}: {}): JSX.Element => {
flows.forEach((flow) => { flows.forEach((flow) => {
tempNameList.push(flow.name); tempNameList.push(flow.name);
}); });
setNameList(tempNameList.filter((name) => name !== currentFlow?.name)); setNameList(tempNameList.filter((name) => name !== currentFlowName));
} }
}, [flows, currentFlow?.name]); }, [flows, currentFlowName]);
useGetRefreshFlowsQuery( useGetRefreshFlowsQuery(
{ {
@ -86,13 +105,10 @@ export const MenuBar = ({}: {}): JSX.Element => {
); );
const currentFolder = useMemo( const currentFolder = useMemo(
() => folders?.find((f) => f.id === currentFlow?.folder_id), () => folders?.find((f) => f.id === currentFlowFolderId),
[folders, currentFlow?.folder_id], [folders, currentFlowFolderId],
); );
const changesNotSaved =
customStringify(currentFlow) !== customStringify(currentSavedFlow);
useEffect(() => { useEffect(() => {
if (measureRef.current) { if (measureRef.current) {
setInputWidth(measureRef.current.offsetWidth); setInputWidth(measureRef.current.offsetWidth);
@ -163,26 +179,29 @@ export const MenuBar = ({}: {}): JSX.Element => {
(e: React.KeyboardEvent<HTMLInputElement>) => { (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") { if (e.key === "Escape") {
setEditingName(false); setEditingName(false);
setFlowName(currentFlow?.name ?? ""); setFlowName(currentFlowName ?? "");
setIsInvalidName(false); setIsInvalidName(false);
} }
if (e.key === "Enter") { if (e.key === "Enter") {
nameInputRef.current?.blur(); nameInputRef.current?.blur();
} }
}, },
[currentFlow?.name], [currentFlowName],
); );
const handleNameSubmit = useCallback(() => { const handleNameSubmit = useCallback(() => {
if ( if (
flowName.trim() !== "" && flowName.trim() !== "" &&
flowName !== currentFlow?.name && flowName !== currentFlowName &&
!isInvalidName !isInvalidName
) { ) {
// Get a one-time snapshot of currentFlow using get()
const currentFlowSnapshot = useFlowStore.getState().currentFlow;
const newFlow = { const newFlow = {
...currentFlow!, ...currentFlowSnapshot!,
name: flowName, name: flowName,
id: currentFlow!.id, id: currentFlowId!,
}; };
setCurrentFlow(newFlow); setCurrentFlow(newFlow);
saveFlow(newFlow) saveFlow(newFlow)
@ -194,22 +213,23 @@ export const MenuBar = ({}: {}): JSX.Element => {
title: "Error updating flow name", title: "Error updating flow name",
list: [(error as Error).message], list: [(error as Error).message],
}); });
setFlowName(currentFlow?.name ?? ""); setFlowName(currentFlowName ?? "");
}); });
} else if (isInvalidName) { } else if (isInvalidName) {
setErrorData({ setErrorData({
title: "Invalid flow name", title: "Invalid flow name",
list: ["Name already exists"], list: ["Name already exists"],
}); });
setFlowName(currentFlow?.name ?? ""); setFlowName(currentFlowName ?? "");
} else { } else {
setFlowName(currentFlow?.name ?? ""); setFlowName(currentFlowName ?? "");
} }
setEditingName(false); setEditingName(false);
setIsInvalidName(false); setIsInvalidName(false);
}, [ }, [
flowName, flowName,
currentFlow, currentFlowName,
currentFlowId,
setCurrentFlow, setCurrentFlow,
saveFlow, saveFlow,
setSuccessData, setSuccessData,
@ -218,10 +238,10 @@ export const MenuBar = ({}: {}): JSX.Element => {
]); ]);
useEffect(() => { useEffect(() => {
if (currentFlow && !editingName) { if (currentFlowName && !editingName) {
setFlowName(currentFlow.name); setFlowName(currentFlowName);
} }
}, [currentFlow, editingName]); }, [currentFlowName, editingName]);
useEffect(() => { useEffect(() => {
if (measureRef.current) { if (measureRef.current) {
@ -230,12 +250,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
}, [flowName]); }, [flowName]);
const swatchIndex = const swatchIndex =
(currentFlow?.gradient && !isNaN(parseInt(currentFlow?.gradient)) (currentFlowGradient && !isNaN(parseInt(currentFlowGradient))
? parseInt(currentFlow?.gradient) ? parseInt(currentFlowGradient)
: getNumberFromString(currentFlow?.gradient ?? currentFlow?.id ?? "")) % : getNumberFromString(currentFlowGradient ?? currentFlowId ?? "")) %
swatchColors.length; swatchColors.length;
return currentFlow && onFlowPage ? ( return currentFlowName && onFlowPage ? (
<div <div
className="flex w-full items-center justify-center gap-2" className="flex w-full items-center justify-center gap-2"
data-testid="menu_bar_wrapper" data-testid="menu_bar_wrapper"
@ -270,7 +290,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
</div> </div>
<div className={cn(`flex rounded p-1`, swatchColors[swatchIndex])}> <div className={cn(`flex rounded p-1`, swatchColors[swatchIndex])}>
<IconComponent <IconComponent
name={currentFlow?.icon ?? "Workflow"} name={currentFlowIcon ?? "Workflow"}
className="h-3.5 w-3.5" className="h-3.5 w-3.5"
/> />
</div> </div>
@ -305,7 +325,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onFocus={() => { onFocus={() => {
setEditingName(true); setEditingName(true);
setFlowName(currentFlow.name); setFlowName(currentFlowName);
}} }}
onBlur={handleNameSubmit} onBlur={handleNameSubmit}
value={flowName} value={flowName}

View file

@ -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);
}

View file

@ -18,8 +18,12 @@ export default function FlowSettingsModal({
flowData, flowData,
details, details,
}: FlowSettingsPropsType): JSX.Element { }: FlowSettingsPropsType): JSX.Element {
if (!open) return <></>;
const saveFlow = useSaveFlow(); const saveFlow = useSaveFlow();
const currentFlow = useFlowStore((state) => state.currentFlow); const currentFlow = useFlowStore((state) =>
flowData ? undefined : state.currentFlow,
);
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const flows = useFlowsManagerStore((state) => state.flows); const flows = useFlowsManagerStore((state) => state.flows);