Skip to content

Commit 956463d

Browse files
committed
Fix race in tab creation by waiting for initial task poll
1 parent d9d48b9 commit 956463d

File tree

1 file changed

+47
-13
lines changed

1 file changed

+47
-13
lines changed

lib/hooks/use-task.ts

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
'use client'
22

3-
import { useState, useEffect, useCallback } from 'react'
3+
import { useState, useEffect, useCallback, useRef } from 'react'
44
import { Task } from '@/lib/db/schema'
55

66
export function useTask(taskId: string) {
77
const [task, setTask] = useState<Task | null>(null)
88
const [isLoading, setIsLoading] = useState(true)
99
const [error, setError] = useState<string | null>(null)
10+
const attemptCountRef = useRef(0)
11+
const hasFoundTaskRef = useRef(false)
1012

1113
const fetchTask = useCallback(async () => {
1214
try {
@@ -15,33 +17,65 @@ export function useTask(taskId: string) {
1517
const data = await response.json()
1618
setTask(data.task)
1719
setError(null)
20+
hasFoundTaskRef.current = true
1821
} else if (response.status === 404) {
19-
setError('Task not found')
20-
setTask(null)
22+
// Only set error after multiple failed attempts (to handle race condition on task creation)
23+
// Wait for at least 3 attempts (up to ~6 seconds) before showing "Task not found"
24+
attemptCountRef.current += 1
25+
if (attemptCountRef.current >= 3 || hasFoundTaskRef.current) {
26+
setError('Task not found')
27+
setTask(null)
28+
}
29+
// If we haven't hit the attempt threshold yet, keep loading state
2130
} else {
2231
setError('Failed to fetch task')
2332
}
2433
} catch (err) {
2534
console.error('Error fetching task:', err)
2635
setError('Failed to fetch task')
2736
} finally {
28-
setIsLoading(false)
37+
// Only stop loading after we've either found the task or exceeded attempt threshold
38+
if (hasFoundTaskRef.current || attemptCountRef.current >= 3 || error) {
39+
setIsLoading(false)
40+
}
2941
}
30-
}, [taskId])
42+
}, [taskId, error])
3143

32-
// Initial fetch
44+
// Initial fetch with retry logic
3345
useEffect(() => {
46+
attemptCountRef.current = 0
47+
hasFoundTaskRef.current = false
48+
setIsLoading(true)
49+
setError(null)
50+
51+
// Fetch immediately
3452
fetchTask()
35-
}, [fetchTask])
3653

37-
// Poll for updates every 5 seconds
54+
// If task isn't found on first try, retry more aggressively initially
55+
// This handles the race condition where we navigate to the task page before the DB insert completes
56+
const retryInterval = setInterval(() => {
57+
if (!hasFoundTaskRef.current && attemptCountRef.current < 3) {
58+
fetchTask()
59+
} else {
60+
clearInterval(retryInterval)
61+
}
62+
}, 2000) // Check every 2 seconds for the first few attempts
63+
64+
return () => clearInterval(retryInterval)
65+
// eslint-disable-next-line react-hooks/exhaustive-deps
66+
}, [taskId]) // fetchTask is intentionally not in deps to avoid recreating interval on every fetchTask change
67+
68+
// Poll for updates every 5 seconds after initial load
3869
useEffect(() => {
39-
const interval = setInterval(() => {
40-
fetchTask()
41-
}, 5000)
70+
// Only start polling after we've found the task or given up
71+
if (!isLoading) {
72+
const interval = setInterval(() => {
73+
fetchTask()
74+
}, 5000)
4275

43-
return () => clearInterval(interval)
44-
}, [fetchTask])
76+
return () => clearInterval(interval)
77+
}
78+
}, [fetchTask, isLoading])
4579

4680
return { task, isLoading, error, refetch: fetchTask }
4781
}

0 commit comments

Comments
 (0)