Skip to content

Commit a12eb3b

Browse files
kutcodeclaude
andcommitted
Fix frontend memory leaks and polling race condition
- Add useEffect cleanup for toast timeouts in 9 components - Add pollingInFlight guard to prevent concurrent poll requests in pollJob/pollBatch on the main upload page Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent d4d6fa7 commit a12eb3b

9 files changed

Lines changed: 54 additions & 0 deletions

File tree

frontend/src/app/page.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,14 @@ export default function UploadPage() {
9595
const [selectedProvider, setSelectedProvider] = useState(null);
9696
const pollRef = useRef(null);
9797
const thinkingRef = useRef(null);
98+
const pollingInFlight = useRef(false);
9899

99100
const toastTimeout = useRef(null);
101+
useEffect(() => {
102+
return () => {
103+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
104+
};
105+
}, []);
100106
const showToast = useCallback((msg, type = 'info') => {
101107
if (toastTimeout.current) clearTimeout(toastTimeout.current);
102108
setToast({ message: msg, type });
@@ -207,6 +213,8 @@ export default function UploadPage() {
207213
}, []);
208214

209215
const pollJob = useCallback(async (jobId) => {
216+
if (pollingInFlight.current) return;
217+
pollingInFlight.current = true;
210218
try {
211219
const job = await getJob(jobId);
212220
setCurrentJob(job);
@@ -229,10 +237,14 @@ export default function UploadPage() {
229237
}
230238
} catch (err) {
231239
stopPolling();
240+
} finally {
241+
pollingInFlight.current = false;
232242
}
233243
}, [refreshJobs, showToast, stopPolling]);
234244

235245
const pollBatch = useCallback(async (batchId) => {
246+
if (pollingInFlight.current) return;
247+
pollingInFlight.current = true;
236248
try {
237249
const batch = await getBatchJobs(batchId);
238250
setCurrentBatch(batch);
@@ -254,6 +266,8 @@ export default function UploadPage() {
254266
}
255267
} catch (err) {
256268
stopPolling();
269+
} finally {
270+
pollingInFlight.current = false;
257271
}
258272
}, [refreshJobs, showToast, stopPolling]);
259273

frontend/src/app/settings/page.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ function SettingsConfigContent() {
125125
const [testingConnection, setTestingConnection] = useState(null);
126126

127127
const toastTimeout = useRef(null);
128+
useEffect(() => {
129+
return () => {
130+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
131+
};
132+
}, []);
128133
const showToast = useCallback((msg, type = 'info') => {
129134
if (toastTimeout.current) clearTimeout(toastTimeout.current);
130135
setToast({ message: msg, type });

frontend/src/app/troubleshoot/page.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ export default function TroubleshootPage() {
6060
const aiFixPlan = expandedResult?.agent_analysis?.fix_plan || null;
6161

6262
const toastTimeout = useRef(null);
63+
useEffect(() => {
64+
return () => {
65+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
66+
};
67+
}, []);
6368
const showToast = (msg, type = 'info') => {
6469
if (toastTimeout.current) clearTimeout(toastTimeout.current);
6570
setToast({ message: msg, type });

frontend/src/components/ActivityLogContent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export default function ActivityLogContent() {
4040
const [expandedId, setExpandedId] = useState(null);
4141

4242
const toastTimeout = useRef(null);
43+
useEffect(() => {
44+
return () => {
45+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
46+
};
47+
}, []);
4348
const showToast = useCallback((msg, type = 'info') => {
4449
if (toastTimeout.current) clearTimeout(toastTimeout.current);
4550
setToast({ message: msg, type });

frontend/src/components/AgentLearningContent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export default function AgentLearningContent() {
2929
const [toast, setToast] = useState(null);
3030

3131
const toastTimeout = useRef(null);
32+
useEffect(() => {
33+
return () => {
34+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
35+
};
36+
}, []);
3237
const showToast = useCallback((msg, type = 'info') => {
3338
if (toastTimeout.current) clearTimeout(toastTimeout.current);
3439
setToast({ message: msg, type });

frontend/src/components/DuplicatesContent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,11 @@ export default function DuplicatesContent() {
438438
const [activeTab, setActiveTab] = useState('review');
439439
const [toast, setToast] = useState(null);
440440
const toastTimeout = useRef(null);
441+
useEffect(() => {
442+
return () => {
443+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
444+
};
445+
}, []);
441446
const showToast = useCallback((msg, type = 'info') => {
442447
if (toastTimeout.current) clearTimeout(toastTimeout.current);
443448
setToast({ message: msg, type });

frontend/src/components/FlaggedContent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export default function FlaggedContent() {
3333
const [confirmAction, setConfirmAction] = useState(null);
3434

3535
const toastTimeout = useRef(null);
36+
useEffect(() => {
37+
return () => {
38+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
39+
};
40+
}, []);
3641
const showToast = useCallback((msg, type = 'info') => {
3742
if (toastTimeout.current) clearTimeout(toastTimeout.current);
3843
setToast({ message: msg, type });

frontend/src/components/KBEntriesContent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export default function KBEntriesContent() {
3535
const [importing, setImporting] = useState(false);
3636

3737
const toastTimeout = useRef(null);
38+
useEffect(() => {
39+
return () => {
40+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
41+
};
42+
}, []);
3843
const showToast = useCallback((msg, type = 'info') => {
3944
if (toastTimeout.current) clearTimeout(toastTimeout.current);
4045
setToast({ message: msg, type });

frontend/src/components/LearnedFormatsContent.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ export default function LearnedFormatsContent() {
1818
const [confirmDelete, setConfirmDelete] = useState(null);
1919

2020
const toastTimeout = useRef(null);
21+
useEffect(() => {
22+
return () => {
23+
if (toastTimeout.current) clearTimeout(toastTimeout.current);
24+
};
25+
}, []);
2126
const showToast = useCallback((msg, type = 'info') => {
2227
if (toastTimeout.current) clearTimeout(toastTimeout.current);
2328
setToast({ message: msg, type });

0 commit comments

Comments
 (0)