Skip to content

Commit b23ba1b

Browse files
fix(table): serialize polling ticks to prevent overlapping fetches
1 parent 3c9afcd commit b23ba1b

1 file changed

Lines changed: 48 additions & 36 deletions

File tree

apps/sim/hooks/queries/tables.ts

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -320,49 +320,61 @@ export function useInfiniteTableRows({
320320
useEffect(() => {
321321
if (!enabled || !workspaceId || !tableId) return
322322
let cancelled = false
323+
let timeoutId: ReturnType<typeof setTimeout> | null = null
323324
const tick = async () => {
324325
if (cancelled) return
325-
if (queryClient.isMutating() > 0) return
326-
const data = queryClient.getQueryData<InfiniteData<TableRowsResponse, number>>(queryKey)
327-
if (!data) return
328-
const dirty: number[] = []
329-
for (let i = 0; i < data.pages.length; i++) {
330-
if (hasRunningGroupExecution(data.pages[i].rows)) {
331-
dirty.push(data.pageParams[i] ?? i * pageSize)
326+
if (queryClient.isMutating() === 0) {
327+
const data = queryClient.getQueryData<InfiniteData<TableRowsResponse, number>>(queryKey)
328+
const dirty: number[] = []
329+
if (data) {
330+
for (let i = 0; i < data.pages.length; i++) {
331+
if (hasRunningGroupExecution(data.pages[i].rows)) {
332+
dirty.push(data.pageParams[i] ?? i * pageSize)
333+
}
334+
}
332335
}
333-
}
334-
if (dirty.length === 0) return
335-
await Promise.all(
336-
dirty.map(async (offset) => {
337-
try {
338-
const fresh = await fetchTableRows({
339-
workspaceId,
340-
tableId,
341-
limit: pageSize,
342-
offset,
343-
filter,
344-
sort,
345-
includeTotal: offset === 0,
336+
if (dirty.length > 0) {
337+
await Promise.all(
338+
dirty.map(async (offset) => {
339+
try {
340+
const fresh = await fetchTableRows({
341+
workspaceId,
342+
tableId,
343+
limit: pageSize,
344+
offset,
345+
filter,
346+
sort,
347+
includeTotal: offset === 0,
348+
})
349+
if (cancelled) return
350+
queryClient.setQueryData<InfiniteData<TableRowsResponse, number>>(
351+
queryKey,
352+
(prev) => {
353+
if (!prev) return prev
354+
const idx = prev.pageParams.indexOf(offset)
355+
if (idx === -1) return prev
356+
const nextPages = prev.pages.slice()
357+
nextPages[idx] = fresh
358+
return { ...prev, pages: nextPages }
359+
}
360+
)
361+
} catch {
362+
// Transient fetch failure — next tick retries. Don't kill the loop.
363+
}
346364
})
347-
if (cancelled) return
348-
queryClient.setQueryData<InfiniteData<TableRowsResponse, number>>(queryKey, (prev) => {
349-
if (!prev) return prev
350-
const idx = prev.pageParams.indexOf(offset)
351-
if (idx === -1) return prev
352-
const nextPages = prev.pages.slice()
353-
nextPages[idx] = fresh
354-
return { ...prev, pages: nextPages }
355-
})
356-
} catch {
357-
// Transient fetch failure — next tick retries. Don't kill the loop.
358-
}
359-
})
360-
)
365+
)
366+
}
367+
}
368+
if (cancelled) return
369+
// Recursive setTimeout instead of setInterval so a slow tick can't
370+
// overlap the next one — out-of-order responses would otherwise let
371+
// stale data overwrite fresh.
372+
timeoutId = setTimeout(() => void tick(), ROWS_POLL_INTERVAL_WHILE_RUNNING_MS)
361373
}
362-
const intervalId = setInterval(() => void tick(), ROWS_POLL_INTERVAL_WHILE_RUNNING_MS)
374+
timeoutId = setTimeout(() => void tick(), ROWS_POLL_INTERVAL_WHILE_RUNNING_MS)
363375
return () => {
364376
cancelled = true
365-
clearInterval(intervalId)
377+
if (timeoutId !== null) clearTimeout(timeoutId)
366378
}
367379
}, [enabled, workspaceId, tableId, pageSize, filter, sort, queryClient, queryKey])
368380

0 commit comments

Comments
 (0)