Skip to content

Commit 945b207

Browse files
committed
fix: memoize validation selector to prevent mutation and unnecessary recomputation
- Use individual selectors for blocks/edges/loops/parallels with useShallow - Memoize validation result with useMemo, only recomputing when deps change - Pass shallow copies of state to validateWorkflowState to prevent any internal mutation from affecting Zustand store state Addresses Bugbot review feedback for #3579
1 parent d646ee1 commit 945b207

File tree

1 file changed

+17
-8
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel

1 file changed

+17
-8
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { memo, useCallback, useEffect, useRef, useState } from 'react'
3+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { ArrowUp, Lock, Square, Unlock } from 'lucide-react'
66
import { useParams, useRouter } from 'next/navigation'
@@ -357,16 +357,25 @@ export const Panel = memo(function Panel() {
357357
// Compute run button state
358358
const canRun = userPermissions.canRead // Running only requires read permissions
359359
const isLoadingPermissions = userPermissions.isLoading
360-
const hasValidationErrors = useWorkflowStore((state) => {
361-
if (Object.keys(state.blocks).length === 0) return false
360+
// Memoize workflow structure to avoid re-validating on every store change
361+
// (e.g. block dragging at 60fps, text input, etc.)
362+
const blocks = useWorkflowStore((state) => state.blocks)
363+
const edges = useWorkflowStore((state) => state.edges)
364+
const loops = useWorkflowStore((state) => state.loops)
365+
const parallels = useWorkflowStore((state) => state.parallels)
366+
367+
const hasValidationErrors = useMemo(() => {
368+
if (Object.keys(blocks).length === 0) return false
369+
// Pass shallow copies to validateWorkflowState to prevent any
370+
// internal mutation from affecting Zustand store state
362371
const result = validateWorkflowState({
363-
blocks: state.blocks,
364-
edges: state.edges,
365-
loops: state.loops || {},
366-
parallels: state.parallels || {},
372+
blocks: { ...blocks },
373+
edges: [...edges],
374+
loops: { ...(loops || {}) },
375+
parallels: { ...(parallels || {}) },
367376
})
368377
return !result.valid
369-
})
378+
}, [blocks, edges, loops, parallels])
370379
const isWorkflowBlocked = isExecuting || hasValidationErrors
371380
const isButtonDisabled = !isExecuting && (isWorkflowBlocked || (!canRun && !isLoadingPermissions))
372381

0 commit comments

Comments
 (0)