From 04bf02b6aad0775c5470e28ffd721d86b78ce134 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Tue, 10 Mar 2026 21:31:47 +1300 Subject: [PATCH] perf: use useShallow for WorkflowCanvas store subscription Split the monolithic useWorkflowStore() destructure into useShallow for data fields (nodes, edges, groups, dimmedNodeIds, etc.) and individual selectors for action references. Actions are stable in Zustand and don't trigger re-renders, while useShallow ensures the component only re-renders when data values actually change rather than on every store mutation. Co-Authored-By: Claude Opus 4.6 --- src/components/WorkflowCanvas.tsx | 32 +++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 8706e915..1a5103b8 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -19,6 +19,7 @@ import { import "@xyflow/react/dist/style.css"; import { useWorkflowStore, WorkflowFile } from "@/store/workflowStore"; +import { useShallow } from "zustand/shallow"; import { useToast } from "@/components/Toast"; import dynamic from "next/dynamic"; import { @@ -264,8 +265,35 @@ const findScrollableAncestor = (target: HTMLElement, deltaX: number, deltaY: num }; export function WorkflowCanvas() { - const { nodes, edges, groups, onNodesChange, onEdgesChange, onConnect, addNode, updateNodeData, loadWorkflow, getNodeById, addToGlobalHistory, setNodeGroupId, executeWorkflow, isModalOpen, showQuickstart, setShowQuickstart, navigationTarget, setNavigationTarget, captureSnapshot, applyEditOperations, setWorkflowMetadata, canvasNavigationSettings, setShortcutsDialogOpen, dimmedNodeIds, regenerateNode, clearWorkflow } = - useWorkflowStore(); + const { nodes, edges, groups, isModalOpen, showQuickstart, navigationTarget, canvasNavigationSettings, dimmedNodeIds } = + useWorkflowStore(useShallow((state) => ({ + nodes: state.nodes, + edges: state.edges, + groups: state.groups, + isModalOpen: state.isModalOpen, + showQuickstart: state.showQuickstart, + navigationTarget: state.navigationTarget, + canvasNavigationSettings: state.canvasNavigationSettings, + dimmedNodeIds: state.dimmedNodeIds, + }))); + const onNodesChange = useWorkflowStore((state) => state.onNodesChange); + const onEdgesChange = useWorkflowStore((state) => state.onEdgesChange); + const onConnect = useWorkflowStore((state) => state.onConnect); + const addNode = useWorkflowStore((state) => state.addNode); + const updateNodeData = useWorkflowStore((state) => state.updateNodeData); + const loadWorkflow = useWorkflowStore((state) => state.loadWorkflow); + const getNodeById = useWorkflowStore((state) => state.getNodeById); + const addToGlobalHistory = useWorkflowStore((state) => state.addToGlobalHistory); + const setNodeGroupId = useWorkflowStore((state) => state.setNodeGroupId); + const executeWorkflow = useWorkflowStore((state) => state.executeWorkflow); + const setShowQuickstart = useWorkflowStore((state) => state.setShowQuickstart); + const setNavigationTarget = useWorkflowStore((state) => state.setNavigationTarget); + const captureSnapshot = useWorkflowStore((state) => state.captureSnapshot); + const applyEditOperations = useWorkflowStore((state) => state.applyEditOperations); + const setWorkflowMetadata = useWorkflowStore((state) => state.setWorkflowMetadata); + const setShortcutsDialogOpen = useWorkflowStore((state) => state.setShortcutsDialogOpen); + const regenerateNode = useWorkflowStore((state) => state.regenerateNode); + const clearWorkflow = useWorkflowStore((state) => state.clearWorkflow); const openAnnotationModal = useAnnotationStore((state) => state.openModal); const { screenToFlowPosition, getViewport, zoomIn, zoomOut, setViewport, setCenter } = useReactFlow(); const { show: showToast } = useToast();