seems alright
This commit is contained in:
parent
ebe7bbb51d
commit
eac1bd0d13
58 changed files with 23125 additions and 153505 deletions
|
|
@ -12,8 +12,16 @@ export interface SavedSearch {
|
|||
created: string;
|
||||
}
|
||||
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
const MAX_POLL_ATTEMPTS = 15;
|
||||
// Exponential backoff: 2s, 3s, 4s, 6s, 8s, 12s, ... capped at 15s.
|
||||
// Caps total wait under a minute while staying responsive for fast jobs.
|
||||
const POLL_BASE_MS = 2000;
|
||||
const POLL_MAX_MS = 15000;
|
||||
const POLL_BACKOFF = 1.5;
|
||||
const MAX_POLL_ATTEMPTS = 8;
|
||||
|
||||
function nextPollDelay(attempt: number): number {
|
||||
return Math.min(POLL_MAX_MS, Math.round(POLL_BASE_MS * Math.pow(POLL_BACKOFF, attempt)));
|
||||
}
|
||||
|
||||
export function useSavedSearches(userId: string | null) {
|
||||
const [searches, setSearches] = useState<SavedSearch[]>([]);
|
||||
|
|
@ -21,14 +29,16 @@ export function useSavedSearches(userId: string | null) {
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const pollAttemptsRef = useRef(0);
|
||||
const pollInFlightRef = useRef(false);
|
||||
const isMountedRef = useRef(true);
|
||||
const userIdRef = useRef(userId);
|
||||
userIdRef.current = userId;
|
||||
|
||||
const stopPolling = useCallback(() => {
|
||||
if (pollTimerRef.current) {
|
||||
clearInterval(pollTimerRef.current);
|
||||
clearTimeout(pollTimerRef.current);
|
||||
pollTimerRef.current = null;
|
||||
}
|
||||
pollAttemptsRef.current = 0;
|
||||
|
|
@ -37,6 +47,15 @@ export function useSavedSearches(userId: string | null) {
|
|||
// Clean up polling on unmount or userId change
|
||||
useEffect(() => stopPolling, [userId, stopPolling]);
|
||||
|
||||
// Mark the hook as unmounted so late-arriving async work doesn't touch state
|
||||
useEffect(() => {
|
||||
isMountedRef.current = true;
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
stopPolling();
|
||||
};
|
||||
}, [stopPolling]);
|
||||
|
||||
const fetchRecords = useCallback(async (uid: string): Promise<SavedSearch[]> => {
|
||||
const records = await pb.collection('saved_searches').getFullList({
|
||||
sort: '-created',
|
||||
|
|
@ -57,28 +76,41 @@ export function useSavedSearches(userId: string | null) {
|
|||
const startPolling = useCallback(() => {
|
||||
if (pollTimerRef.current) return;
|
||||
pollAttemptsRef.current = 0;
|
||||
pollTimerRef.current = setInterval(async () => {
|
||||
pollInFlightRef.current = false;
|
||||
|
||||
const scheduleNext = () => {
|
||||
if (!isMountedRef.current) return;
|
||||
const delay = nextPollDelay(pollAttemptsRef.current);
|
||||
pollTimerRef.current = setTimeout(tick, delay);
|
||||
};
|
||||
|
||||
const tick = async () => {
|
||||
pollTimerRef.current = null;
|
||||
if (pollInFlightRef.current) {
|
||||
scheduleNext();
|
||||
return;
|
||||
}
|
||||
const uid = userIdRef.current;
|
||||
if (!uid) {
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
if (!uid) return;
|
||||
pollAttemptsRef.current++;
|
||||
if (pollAttemptsRef.current >= MAX_POLL_ATTEMPTS) {
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
if (pollAttemptsRef.current > MAX_POLL_ATTEMPTS) return;
|
||||
pollInFlightRef.current = true;
|
||||
try {
|
||||
const mapped = await fetchRecords(uid);
|
||||
if (!isMountedRef.current) return;
|
||||
setSearches(mapped);
|
||||
if (!mapped.some((s) => !s.screenshotUrl)) {
|
||||
stopPolling();
|
||||
}
|
||||
if (!mapped.some((s) => !s.screenshotUrl)) return;
|
||||
scheduleNext();
|
||||
} catch {
|
||||
// Silent — background poll errors don't surface to UI
|
||||
// Silent — background poll errors don't surface to UI; keep trying.
|
||||
if (isMountedRef.current) scheduleNext();
|
||||
} finally {
|
||||
pollInFlightRef.current = false;
|
||||
}
|
||||
}, POLL_INTERVAL_MS);
|
||||
}, [stopPolling, fetchRecords]);
|
||||
};
|
||||
|
||||
scheduleNext();
|
||||
}, [fetchRecords]);
|
||||
|
||||
const fetchSearches = useCallback(async () => {
|
||||
if (!userId) return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue