fix: prevent promise errors in async operations after component unmount
This commit addresses the "Uncaught (in promise) Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received" error that occurs when async operations attempt to update state after component unmount. Changes: - Add component mount status tracking with useRef in App.tsx and DocumentManager.tsx - Implement beforeunload event listeners to handle page reload scenarios - Add mount status checks before and after async operations - Add try-catch blocks to properly handle errors in async operations - Ensure state updates only occur when components are still mounted - Prevent health check and document polling from causing errors during unmount
This commit is contained in:
@@ -33,6 +33,26 @@ function App() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Track component mount status with useRef
|
||||
const isMountedRef = useRef(true);
|
||||
|
||||
// Set up mount/unmount status tracking
|
||||
useEffect(() => {
|
||||
isMountedRef.current = true;
|
||||
|
||||
// Handle page reload/unload
|
||||
const handleBeforeUnload = () => {
|
||||
isMountedRef.current = false;
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Health check - can be disabled
|
||||
useEffect(() => {
|
||||
// Only execute if health check is enabled and ApiKeyAlert is closed
|
||||
@@ -40,7 +60,14 @@ function App() {
|
||||
|
||||
// Health check function
|
||||
const performHealthCheck = async () => {
|
||||
await useBackendState.getState().check();
|
||||
try {
|
||||
// Only perform health check if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
await useBackendState.getState().check();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Health check error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Set interval for periodic execution
|
||||
|
@@ -137,6 +137,26 @@ type SortField = 'created_at' | 'updated_at' | 'id';
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export default function DocumentManager() {
|
||||
// Track component mount status
|
||||
const isMountedRef = useRef(true);
|
||||
|
||||
// Set up mount/unmount status tracking
|
||||
useEffect(() => {
|
||||
isMountedRef.current = true;
|
||||
|
||||
// Handle page reload/unload
|
||||
const handleBeforeUnload = () => {
|
||||
isMountedRef.current = false;
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [showPipelineStatus, setShowPipelineStatus] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const health = useBackendState.use.health()
|
||||
@@ -324,7 +344,13 @@ export default function DocumentManager() {
|
||||
|
||||
const fetchDocuments = useCallback(async () => {
|
||||
try {
|
||||
const docs = await getDocuments()
|
||||
// Check if component is still mounted before starting the request
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
const docs = await getDocuments();
|
||||
|
||||
// Check again if component is still mounted after the request completes
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
// Get new status counts (treat null as all zeros)
|
||||
const newStatusCounts = {
|
||||
@@ -339,30 +365,36 @@ export default function DocumentManager() {
|
||||
status => newStatusCounts[status] !== prevStatusCounts.current[status]
|
||||
)
|
||||
|
||||
// Trigger health check if changes detected
|
||||
if (hasStatusCountChange) {
|
||||
// Trigger health check if changes detected and component is still mounted
|
||||
if (hasStatusCountChange && isMountedRef.current) {
|
||||
useBackendState.getState().check()
|
||||
}
|
||||
|
||||
// Update previous status counts
|
||||
prevStatusCounts.current = newStatusCounts
|
||||
// Only update state if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
// Update previous status counts
|
||||
prevStatusCounts.current = newStatusCounts
|
||||
|
||||
// Update docs state
|
||||
if (docs && docs.statuses) {
|
||||
const numDocuments = Object.values(docs.statuses).reduce(
|
||||
(acc, status) => acc + status.length,
|
||||
0
|
||||
)
|
||||
if (numDocuments > 0) {
|
||||
setDocs(docs)
|
||||
// Update docs state
|
||||
if (docs && docs.statuses) {
|
||||
const numDocuments = Object.values(docs.statuses).reduce(
|
||||
(acc, status) => acc + status.length,
|
||||
0
|
||||
)
|
||||
if (numDocuments > 0) {
|
||||
setDocs(docs)
|
||||
} else {
|
||||
setDocs(null)
|
||||
}
|
||||
} else {
|
||||
setDocs(null)
|
||||
}
|
||||
} else {
|
||||
setDocs(null)
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }))
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }))
|
||||
}
|
||||
}
|
||||
}, [setDocs, t])
|
||||
|
||||
@@ -375,10 +407,20 @@ export default function DocumentManager() {
|
||||
|
||||
const scanDocuments = useCallback(async () => {
|
||||
try {
|
||||
const { status } = await scanNewDocuments()
|
||||
toast.message(status)
|
||||
// Check if component is still mounted before starting the request
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
const { status } = await scanNewDocuments();
|
||||
|
||||
// Check again if component is still mounted after the request completes
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
toast.message(status);
|
||||
} catch (err) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }))
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }));
|
||||
}
|
||||
}
|
||||
}, [t])
|
||||
|
||||
@@ -390,13 +432,21 @@ export default function DocumentManager() {
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
await fetchDocuments()
|
||||
// Only perform fetch if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
await fetchDocuments()
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
|
||||
}
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, [health, fetchDocuments, t, currentTab])
|
||||
|
||||
// Add dependency on sort state to re-render when sort changes
|
||||
|
Reference in New Issue
Block a user