mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
fix(workflow)!: add mounted guard to prevent ReactFlow operations after unmount
When switching from graph view to skill view during an active preview run, SSE callbacks continue executing and attempt to update ReactFlow node/edge states. This could cause errors since the component is unmounted. Add optional `isMountedRef` parameter to `useNodesInteractionsWithoutSync` and `useEdgesInteractionsWithoutSync` hooks. When provided, operations are skipped if the component has unmounted, preventing potential errors while allowing the SSE connection to continue running in the background. BREAKING CHANGE: `useNodesInteractionsWithoutSync` and `useEdgesInteractionsWithoutSync` now accept an optional `isMountedRef` parameter. Existing callers are unaffected as the parameter is optional.
This commit is contained in:
@ -1,11 +1,15 @@
|
|||||||
|
import type { RefObject } from 'react'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
|
|
||||||
export const useEdgesInteractionsWithoutSync = () => {
|
export const useEdgesInteractionsWithoutSync = (isMountedRef?: RefObject<boolean>) => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
|
|
||||||
const handleEdgeCancelRunningStatus = useCallback(() => {
|
const handleEdgeCancelRunningStatus = useCallback(() => {
|
||||||
|
if (isMountedRef && isMountedRef.current === false)
|
||||||
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
edges,
|
edges,
|
||||||
setEdges,
|
setEdges,
|
||||||
@ -19,7 +23,7 @@ export const useEdgesInteractionsWithoutSync = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}, [store])
|
}, [store, isMountedRef])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleEdgeCancelRunningStatus,
|
handleEdgeCancelRunningStatus,
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
|
import type { RefObject } from 'react'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
import { NodeRunningStatus } from '../types'
|
import { NodeRunningStatus } from '../types'
|
||||||
|
|
||||||
export const useNodesInteractionsWithoutSync = () => {
|
export const useNodesInteractionsWithoutSync = (isMountedRef?: RefObject<boolean>) => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
|
|
||||||
const handleNodeCancelRunningStatus = useCallback(() => {
|
const handleNodeCancelRunningStatus = useCallback(() => {
|
||||||
|
if (isMountedRef && isMountedRef.current === false)
|
||||||
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
@ -20,9 +24,12 @@ export const useNodesInteractionsWithoutSync = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}, [store])
|
}, [store, isMountedRef])
|
||||||
|
|
||||||
const handleCancelAllNodeSuccessStatus = useCallback(() => {
|
const handleCancelAllNodeSuccessStatus = useCallback(() => {
|
||||||
|
if (isMountedRef && isMountedRef.current === false)
|
||||||
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
@ -36,9 +43,12 @@ export const useNodesInteractionsWithoutSync = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}, [store])
|
}, [store, isMountedRef])
|
||||||
|
|
||||||
const handleCancelNodeSuccessStatus = useCallback((nodeId: string) => {
|
const handleCancelNodeSuccessStatus = useCallback((nodeId: string) => {
|
||||||
|
if (isMountedRef && isMountedRef.current === false)
|
||||||
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
@ -52,7 +62,7 @@ export const useNodesInteractionsWithoutSync = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}, [store])
|
}, [store, isMountedRef])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleNodeCancelRunningStatus,
|
handleNodeCancelRunningStatus,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
import { DEFAULT_ITER_TIMES, DEFAULT_LOOP_TIMES } from '../../../constants'
|
import { DEFAULT_ITER_TIMES, DEFAULT_LOOP_TIMES } from '../../../constants'
|
||||||
import { useEdgesInteractionsWithoutSync } from '../../../hooks/use-edges-interactions-without-sync'
|
import { useEdgesInteractionsWithoutSync } from '../../../hooks/use-edges-interactions-without-sync'
|
||||||
import { useNodesInteractionsWithoutSync } from '../../../hooks/use-nodes-interactions-without-sync'
|
import { useNodesInteractionsWithoutSync } from '../../../hooks/use-nodes-interactions-without-sync'
|
||||||
@ -19,8 +19,14 @@ export function useChatFlowControl({
|
|||||||
const setHasStopResponded = useStore(s => s.setHasStopResponded)
|
const setHasStopResponded = useStore(s => s.setHasStopResponded)
|
||||||
const setSuggestedQuestionsAbortController = useStore(s => s.setSuggestedQuestionsAbortController)
|
const setSuggestedQuestionsAbortController = useStore(s => s.setSuggestedQuestionsAbortController)
|
||||||
const invalidateRun = useStore(s => s.invalidateRun)
|
const invalidateRun = useStore(s => s.invalidateRun)
|
||||||
const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync()
|
|
||||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
const isMountedRef = useRef(true)
|
||||||
|
useEffect(() => () => {
|
||||||
|
isMountedRef.current = false
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync(isMountedRef)
|
||||||
|
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync(isMountedRef)
|
||||||
|
|
||||||
const { setIterTimes, setLoopTimes } = workflowStore.getState()
|
const { setIterTimes, setLoopTimes } = workflowStore.getState()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user