Description
Three critical issues in TaskDetails.tsx:
(1) getPluginSchema promise updates state after unmount
(2) loadTask() called without race protection
(3) state setters without mounted check in useTaskSubscription
Current Code - Issue 1 (Line ~420)
getPluginSchema(statusData.plugin_id).then(setSchema).catch(() => setSchema(null))
Current Code - Issue 2 (Line ~376)
onStatus: (status) => {
setTask((prev: Task | null) => prev ? { ...prev, status } : null)
if (['completed', 'failed', 'cancelled'].includes(status)) {
loadTask()
}
}
Current Code - Issue 3 (Lines 365-380)
onStatus: (status) => {
setTask((prev: Task | null) => prev ? { ...prev, status } : null)
if (['completed', 'failed', 'cancelled'].includes(status)) {
loadTask()
}
},
onPhase: (phase) => {
setScanPhase(phase)
},
onOutput: (chunk) => {
setRawOutput((prev) => prev + chunk)
}
Proposed Fix
// Add mounted ref at component top
const isMounted = useRef(true)
useEffect(() => {
return () => { isMounted.current = false }
}, [])
// Update getPluginSchema with mount check
getPluginSchema(statusData.plugin_id)
.then(schema => isMounted.current && setSchema(schema))
.catch(() => isMounted.current && setSchema(null))
// Add request sequence for loadTask race protection
const loadTaskSeqRef = useRef(0)
async function loadTask() {
const currentSeq = ++loadTaskSeqRef.current
if (!isMounted.current) return
// ... rest of function with sequence checks
}
// Add mount checks to all state setters in useTaskSubscription
onStatus: (status) => {
if (!isMounted.current) return
setTask((prev: Task | null) => prev ? { ...prev, status } : null)
if (['completed', 'failed', 'cancelled'].includes(status)) {
loadTask()
}
},
onPhase: (phase) => {
if (!isMounted.current) return
setScanPhase(phase)
},
onOutput: (chunk) => {
if (!isMounted.current) return
setRawOutput((prev) => prev + chunk)
}
Files Changed
- src/pages/TaskDetails.tsx
/label ~bug ~critical ~performance
Description
Three critical issues in TaskDetails.tsx:
(1) getPluginSchema promise updates state after unmount
(2) loadTask() called without race protection
(3) state setters without mounted check in useTaskSubscription
Current Code - Issue 1 (Line ~420)
getPluginSchema(statusData.plugin_id).then(setSchema).catch(() => setSchema(null))
Current Code - Issue 2 (Line ~376)
onStatus: (status) => {
setTask((prev: Task | null) => prev ? { ...prev, status } : null)
if (['completed', 'failed', 'cancelled'].includes(status)) {
loadTask()
}
}
Current Code - Issue 3 (Lines 365-380)
onStatus: (status) => {
setTask((prev: Task | null) => prev ? { ...prev, status } : null)
if (['completed', 'failed', 'cancelled'].includes(status)) {
loadTask()
}
},
onPhase: (phase) => {
setScanPhase(phase)
},
onOutput: (chunk) => {
setRawOutput((prev) => prev + chunk)
}
Proposed Fix
// Add mounted ref at component top
const isMounted = useRef(true)
useEffect(() => {
return () => { isMounted.current = false }
}, [])
// Update getPluginSchema with mount check
getPluginSchema(statusData.plugin_id)
.then(schema => isMounted.current && setSchema(schema))
.catch(() => isMounted.current && setSchema(null))
// Add request sequence for loadTask race protection
const loadTaskSeqRef = useRef(0)
async function loadTask() {
const currentSeq = ++loadTaskSeqRef.current
if (!isMounted.current) return
// ... rest of function with sequence checks
}
// Add mount checks to all state setters in useTaskSubscription
onStatus: (status) => {
if (!isMounted.current) return
setTask((prev: Task | null) => prev ? { ...prev, status } : null)
if (['completed', 'failed', 'cancelled'].includes(status)) {
loadTask()
}
},
onPhase: (phase) => {
if (!isMounted.current) return
setScanPhase(phase)
},
onOutput: (chunk) => {
if (!isMounted.current) return
setRawOutput((prev) => prev + chunk)
}
Files Changed
/label ~bug ~critical ~performance