mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-28 00:08:21 -05:00
improvement(workflow): remove useEffect anti-patterns
This commit is contained in:
@@ -307,9 +307,8 @@ const WorkflowContent = React.memo(() => {
|
||||
|
||||
const isAutoConnectEnabled = useAutoConnect()
|
||||
const autoConnectRef = useRef(isAutoConnectEnabled)
|
||||
useEffect(() => {
|
||||
autoConnectRef.current = isAutoConnectEnabled
|
||||
}, [isAutoConnectEnabled])
|
||||
// Keep ref in sync with latest value for use in callbacks (no effect needed)
|
||||
autoConnectRef.current = isAutoConnectEnabled
|
||||
|
||||
// Panel open states for context menu
|
||||
const isVariablesOpen = useVariablesStore((state) => state.isOpen)
|
||||
@@ -448,11 +447,14 @@ const WorkflowContent = React.memo(() => {
|
||||
)
|
||||
|
||||
/** Re-applies diff markers when blocks change after socket rehydration. */
|
||||
const blocksRef = useRef(blocks)
|
||||
const diffBlocksRef = useRef(blocks)
|
||||
useEffect(() => {
|
||||
if (!isWorkflowReady) return
|
||||
if (hasActiveDiff && isDiffReady && blocks !== blocksRef.current) {
|
||||
blocksRef.current = blocks
|
||||
// Track if blocks actually changed (vs other deps triggering this effect)
|
||||
const blocksChanged = blocks !== diffBlocksRef.current
|
||||
diffBlocksRef.current = blocks
|
||||
|
||||
if (!isWorkflowReady || !blocksChanged) return
|
||||
if (hasActiveDiff && isDiffReady) {
|
||||
setTimeout(() => reapplyDiffMarkers(), 0)
|
||||
}
|
||||
}, [blocks, hasActiveDiff, isDiffReady, reapplyDiffMarkers, isWorkflowReady])
|
||||
@@ -2160,6 +2162,8 @@ const WorkflowContent = React.memo(() => {
|
||||
// Local state for nodes - allows smooth drag without store updates on every frame
|
||||
const [displayNodes, setDisplayNodes] = useState<Node[]>([])
|
||||
|
||||
// Sync derivedNodes to displayNodes while preserving selection state
|
||||
// This effect handles both normal sync and pending selection from paste/duplicate
|
||||
useEffect(() => {
|
||||
// Check for pending selection (from paste/duplicate), otherwise preserve existing selection
|
||||
if (pendingSelection && pendingSelection.length > 0) {
|
||||
@@ -2186,7 +2190,7 @@ const WorkflowContent = React.memo(() => {
|
||||
selected: selectedIds.has(node.id),
|
||||
}))
|
||||
})
|
||||
}, [derivedNodes, blocks, pendingSelection, clearPendingSelection])
|
||||
}, [derivedNodes, blocks, pendingSelection, clearPendingSelection, syncPanelWithSelection])
|
||||
|
||||
// Phase 2: When displayNodes updates, check if pending zoom blocks are ready
|
||||
// (Phase 1 is located earlier in the file where pendingZoomBlockIdsRef is defined)
|
||||
@@ -2380,40 +2384,6 @@ const WorkflowContent = React.memo(() => {
|
||||
resizeLoopNodesWrapper()
|
||||
}, [derivedNodes, resizeLoopNodesWrapper, isWorkflowReady])
|
||||
|
||||
/** Cleans up orphaned nodes with invalid parent references after deletion. */
|
||||
useEffect(() => {
|
||||
if (!isWorkflowReady) return
|
||||
|
||||
// Create a mapping of node IDs to check for missing parent references
|
||||
const nodeIds = new Set(Object.keys(blocks))
|
||||
|
||||
// Check for nodes with invalid parent references and collect updates
|
||||
const orphanedUpdates: Array<{
|
||||
id: string
|
||||
position: { x: number; y: number }
|
||||
parentId: string
|
||||
}> = []
|
||||
Object.entries(blocks).forEach(([id, block]) => {
|
||||
const parentId = block.data?.parentId
|
||||
|
||||
// If block has a parent reference but parent no longer exists
|
||||
if (parentId && !nodeIds.has(parentId)) {
|
||||
logger.warn('Found orphaned node with invalid parent reference', {
|
||||
nodeId: id,
|
||||
missingParentId: parentId,
|
||||
})
|
||||
|
||||
const absolutePosition = getNodeAbsolutePosition(id)
|
||||
orphanedUpdates.push({ id, position: absolutePosition, parentId: '' })
|
||||
}
|
||||
})
|
||||
|
||||
// Batch update all orphaned nodes at once
|
||||
if (orphanedUpdates.length > 0) {
|
||||
batchUpdateBlocksWithParent(orphanedUpdates)
|
||||
}
|
||||
}, [blocks, batchUpdateBlocksWithParent, getNodeAbsolutePosition, isWorkflowReady])
|
||||
|
||||
/** Handles edge removal changes. */
|
||||
const onEdgesChange = useCallback(
|
||||
(changes: any) => {
|
||||
|
||||
@@ -448,6 +448,34 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
delete newBlocks[blockId]
|
||||
})
|
||||
|
||||
// Clean up orphaned nodes - blocks whose parent was removed but weren't descendants
|
||||
// This can happen in edge cases (e.g., data inconsistency, external modifications)
|
||||
const remainingBlockIds = new Set(Object.keys(newBlocks))
|
||||
Object.entries(newBlocks).forEach(([blockId, block]) => {
|
||||
const parentId = block.data?.parentId
|
||||
if (parentId && !remainingBlockIds.has(parentId)) {
|
||||
// Parent was removed - convert to absolute position and clear parentId
|
||||
// Calculate absolute position by traversing up the (now-deleted) parent chain
|
||||
let absoluteX = block.position.x
|
||||
let absoluteY = block.position.y
|
||||
|
||||
// Try to get parent's position from original blocks before deletion
|
||||
let currentParentId: string | undefined = parentId
|
||||
while (currentParentId && currentBlocks[currentParentId]) {
|
||||
const parent = currentBlocks[currentParentId]
|
||||
absoluteX += parent.position.x
|
||||
absoluteY += parent.position.y
|
||||
currentParentId = parent.data?.parentId
|
||||
}
|
||||
|
||||
newBlocks[blockId] = {
|
||||
...block,
|
||||
position: { x: absoluteX, y: absoluteY },
|
||||
data: { ...block.data, parentId: undefined },
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
if (activeWorkflowId) {
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
|
||||
Reference in New Issue
Block a user