@@ -2319,6 +2319,8 @@ const WorkflowContent = React.memo(() => {
23192319 /**
23202320 * Handles connection drag end. Detects if the edge was dropped over a block
23212321 * and automatically creates a connection to that block's target handle.
2322+ * Only creates a connection if ReactFlow didn't already handle it (e.g., when
2323+ * dropping on the block body instead of a handle).
23222324 */
23232325 const onConnectEnd = useCallback (
23242326 ( event : MouseEvent | TouchEvent ) => {
@@ -2340,14 +2342,25 @@ const WorkflowContent = React.memo(() => {
23402342 // Find node under cursor
23412343 const targetNode = findNodeAtPosition ( flowPosition )
23422344
2343- // Create connection if valid target found
2345+ // Create connection if valid target found AND edge doesn't already exist
2346+ // ReactFlow's onConnect fires first when dropping on a handle, so we check
2347+ // if that connection already exists to avoid creating duplicates.
2348+ // IMPORTANT: We must read directly from the store (not React state) because
2349+ // the store update from ReactFlow's onConnect may not have triggered a
2350+ // React re-render yet when this callback runs (typically 1-2ms later).
23442351 if ( targetNode && targetNode . id !== source . nodeId ) {
2345- onConnect ( {
2346- source : source . nodeId ,
2347- sourceHandle : source . handleId ,
2348- target : targetNode . id ,
2349- targetHandle : 'target' ,
2350- } )
2352+ const currentEdges = useWorkflowStore . getState ( ) . edges
2353+ const edgeAlreadyExists = currentEdges . some (
2354+ ( e ) => e . source === source . nodeId && e . target === targetNode . id
2355+ )
2356+ if ( ! edgeAlreadyExists ) {
2357+ onConnect ( {
2358+ source : source . nodeId ,
2359+ sourceHandle : source . handleId ,
2360+ target : targetNode . id ,
2361+ targetHandle : 'target' ,
2362+ } )
2363+ }
23512364 }
23522365
23532366 connectionSourceRef . current = null
0 commit comments