yangdx commited on
Commit
e2bf860
·
1 Parent(s): 665092e

fix: prevent promise errors in async operations after component unmount

Browse files

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

lightrag_webui/src/App.tsx CHANGED
@@ -33,6 +33,26 @@ function App() {
33
  }
34
  }, [])
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  // Health check - can be disabled
37
  useEffect(() => {
38
  // Only execute if health check is enabled and ApiKeyAlert is closed
@@ -40,7 +60,14 @@ function App() {
40
 
41
  // Health check function
42
  const performHealthCheck = async () => {
43
- await useBackendState.getState().check();
 
 
 
 
 
 
 
44
  };
45
 
46
  // Set interval for periodic execution
 
33
  }
34
  }, [])
35
 
36
+ // Track component mount status with useRef
37
+ const isMountedRef = useRef(true);
38
+
39
+ // Set up mount/unmount status tracking
40
+ useEffect(() => {
41
+ isMountedRef.current = true;
42
+
43
+ // Handle page reload/unload
44
+ const handleBeforeUnload = () => {
45
+ isMountedRef.current = false;
46
+ };
47
+
48
+ window.addEventListener('beforeunload', handleBeforeUnload);
49
+
50
+ return () => {
51
+ isMountedRef.current = false;
52
+ window.removeEventListener('beforeunload', handleBeforeUnload);
53
+ };
54
+ }, []);
55
+
56
  // Health check - can be disabled
57
  useEffect(() => {
58
  // Only execute if health check is enabled and ApiKeyAlert is closed
 
60
 
61
  // Health check function
62
  const performHealthCheck = async () => {
63
+ try {
64
+ // Only perform health check if component is still mounted
65
+ if (isMountedRef.current) {
66
+ await useBackendState.getState().check();
67
+ }
68
+ } catch (error) {
69
+ console.error('Health check error:', error);
70
+ }
71
  };
72
 
73
  // Set interval for periodic execution
lightrag_webui/src/features/DocumentManager.tsx CHANGED
@@ -137,6 +137,26 @@ type SortField = 'created_at' | 'updated_at' | 'id';
137
  type SortDirection = 'asc' | 'desc';
138
 
139
  export default function DocumentManager() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  const [showPipelineStatus, setShowPipelineStatus] = useState(false)
141
  const { t } = useTranslation()
142
  const health = useBackendState.use.health()
@@ -324,7 +344,13 @@ export default function DocumentManager() {
324
 
325
  const fetchDocuments = useCallback(async () => {
326
  try {
327
- const docs = await getDocuments()
 
 
 
 
 
 
328
 
329
  // Get new status counts (treat null as all zeros)
330
  const newStatusCounts = {
@@ -339,30 +365,36 @@ export default function DocumentManager() {
339
  status => newStatusCounts[status] !== prevStatusCounts.current[status]
340
  )
341
 
342
- // Trigger health check if changes detected
343
- if (hasStatusCountChange) {
344
  useBackendState.getState().check()
345
  }
346
 
347
- // Update previous status counts
348
- prevStatusCounts.current = newStatusCounts
349
-
350
- // Update docs state
351
- if (docs && docs.statuses) {
352
- const numDocuments = Object.values(docs.statuses).reduce(
353
- (acc, status) => acc + status.length,
354
- 0
355
- )
356
- if (numDocuments > 0) {
357
- setDocs(docs)
 
 
 
 
 
358
  } else {
359
  setDocs(null)
360
  }
361
- } else {
362
- setDocs(null)
363
  }
364
  } catch (err) {
365
- toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }))
 
 
 
366
  }
367
  }, [setDocs, t])
368
 
@@ -375,10 +407,20 @@ export default function DocumentManager() {
375
 
376
  const scanDocuments = useCallback(async () => {
377
  try {
378
- const { status } = await scanNewDocuments()
379
- toast.message(status)
 
 
 
 
 
 
 
380
  } catch (err) {
381
- toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }))
 
 
 
382
  }
383
  }, [t])
384
 
@@ -390,13 +432,21 @@ export default function DocumentManager() {
390
 
391
  const interval = setInterval(async () => {
392
  try {
393
- await fetchDocuments()
 
 
 
394
  } catch (err) {
395
- toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
 
 
 
396
  }
397
  }, 5000)
398
 
399
- return () => clearInterval(interval)
 
 
400
  }, [health, fetchDocuments, t, currentTab])
401
 
402
  // Add dependency on sort state to re-render when sort changes
 
137
  type SortDirection = 'asc' | 'desc';
138
 
139
  export default function DocumentManager() {
140
+ // Track component mount status
141
+ const isMountedRef = useRef(true);
142
+
143
+ // Set up mount/unmount status tracking
144
+ useEffect(() => {
145
+ isMountedRef.current = true;
146
+
147
+ // Handle page reload/unload
148
+ const handleBeforeUnload = () => {
149
+ isMountedRef.current = false;
150
+ };
151
+
152
+ window.addEventListener('beforeunload', handleBeforeUnload);
153
+
154
+ return () => {
155
+ isMountedRef.current = false;
156
+ window.removeEventListener('beforeunload', handleBeforeUnload);
157
+ };
158
+ }, []);
159
+
160
  const [showPipelineStatus, setShowPipelineStatus] = useState(false)
161
  const { t } = useTranslation()
162
  const health = useBackendState.use.health()
 
344
 
345
  const fetchDocuments = useCallback(async () => {
346
  try {
347
+ // Check if component is still mounted before starting the request
348
+ if (!isMountedRef.current) return;
349
+
350
+ const docs = await getDocuments();
351
+
352
+ // Check again if component is still mounted after the request completes
353
+ if (!isMountedRef.current) return;
354
 
355
  // Get new status counts (treat null as all zeros)
356
  const newStatusCounts = {
 
365
  status => newStatusCounts[status] !== prevStatusCounts.current[status]
366
  )
367
 
368
+ // Trigger health check if changes detected and component is still mounted
369
+ if (hasStatusCountChange && isMountedRef.current) {
370
  useBackendState.getState().check()
371
  }
372
 
373
+ // Only update state if component is still mounted
374
+ if (isMountedRef.current) {
375
+ // Update previous status counts
376
+ prevStatusCounts.current = newStatusCounts
377
+
378
+ // Update docs state
379
+ if (docs && docs.statuses) {
380
+ const numDocuments = Object.values(docs.statuses).reduce(
381
+ (acc, status) => acc + status.length,
382
+ 0
383
+ )
384
+ if (numDocuments > 0) {
385
+ setDocs(docs)
386
+ } else {
387
+ setDocs(null)
388
+ }
389
  } else {
390
  setDocs(null)
391
  }
 
 
392
  }
393
  } catch (err) {
394
+ // Only show error if component is still mounted
395
+ if (isMountedRef.current) {
396
+ toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }))
397
+ }
398
  }
399
  }, [setDocs, t])
400
 
 
407
 
408
  const scanDocuments = useCallback(async () => {
409
  try {
410
+ // Check if component is still mounted before starting the request
411
+ if (!isMountedRef.current) return;
412
+
413
+ const { status } = await scanNewDocuments();
414
+
415
+ // Check again if component is still mounted after the request completes
416
+ if (!isMountedRef.current) return;
417
+
418
+ toast.message(status);
419
  } catch (err) {
420
+ // Only show error if component is still mounted
421
+ if (isMountedRef.current) {
422
+ toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }));
423
+ }
424
  }
425
  }, [t])
426
 
 
432
 
433
  const interval = setInterval(async () => {
434
  try {
435
+ // Only perform fetch if component is still mounted
436
+ if (isMountedRef.current) {
437
+ await fetchDocuments()
438
+ }
439
  } catch (err) {
440
+ // Only show error if component is still mounted
441
+ if (isMountedRef.current) {
442
+ toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
443
+ }
444
  }
445
  }, 5000)
446
 
447
+ return () => {
448
+ clearInterval(interval)
449
+ }
450
  }, [health, fetchDocuments, t, currentTab])
451
 
452
  // Add dependency on sort state to re-render when sort changes