diff --git a/src/pages/ethereum/clients/hooks/useDailyByClient.ts b/src/pages/ethereum/clients/hooks/useDailyByClient.ts index ddacc6548..964522613 100644 --- a/src/pages/ethereum/clients/hooks/useDailyByClient.ts +++ b/src/pages/ethereum/clients/hooks/useDailyByClient.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { useQueries, type UseQueryResult } from '@tanstack/react-query'; +import { $ZodError } from 'zod/v4/core'; import { useNetwork } from '@/hooks/useNetwork'; import { useTableBounds } from '@/hooks/useBounds'; import { fetchAllPages } from '@/utils/api-pagination'; @@ -9,6 +10,13 @@ import { chunksFor, useKeepPrevious } from './useVersionCounts'; type ListFn = Parameters[0]; +/** Retries a failed chunk a few times, unlike the app default of forever, so one broken table settles as an error + * instead of loading forever and holding back the pages that wait on it. A request the API client rejects before + * sending fails the same way every time, so it is not retried. */ +function retryChunk(failureCount: number, error: unknown): boolean { + return !(error instanceof $ZodError) && failureCount < 3; +} + export interface UseDailyByClientResult { rows: T[]; /** First day the table has data for (unix seconds), when known */ @@ -56,6 +64,7 @@ export function useDailyByClient( fetchAllPages(list, { query: { ...chunk.query, ...filters, page_size: PAGE_SIZE } }, dataKey, signal), enabled: !!currentNetwork && filters !== null && !bounds.isLoading, staleTime: chunk.open ? 10 * 60 * 1000 : Infinity, + retry: retryChunk, })), combine: combine, }); diff --git a/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts b/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts index 8539fb96e..c9f480d27 100644 --- a/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts +++ b/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts @@ -20,6 +20,15 @@ describe('chunksFor', () => { expect(chunks).toHaveLength(1); expect(chunks[0].query).toEqual({ day_start_date_in_values: '2026-11-03,2026-11-04' }); }); + + it('reads nothing when the data starts after the last complete day', () => { + expect(chunksFor('daily', to + 7 * 3600, to)).toEqual([]); + expect(chunksFor('daily', to, to)).toEqual([]); + }); + + it('still lists the last complete day when the data starts during it', () => { + expect(chunksFor('daily', to - 3600, to)[0].query).toEqual({ day_start_date_in_values: '2026-11-04' }); + }); }); describe('weekChunks', () => { diff --git a/src/pages/ethereum/clients/hooks/useVersionCounts.ts b/src/pages/ethereum/clients/hooks/useVersionCounts.ts index e5c814d41..89d9ff97f 100644 --- a/src/pages/ethereum/clients/hooks/useVersionCounts.ts +++ b/src/pages/ethereum/clients/hooks/useVersionCounts.ts @@ -82,6 +82,9 @@ export function chunksFor(grain: UseVersionCountsOptions['grain'], from: number return chunks; } + // No complete day yet, such as a table whose data starts today: an empty day list is not a valid query + if (from !== undefined && Math.floor(from / DAY) * DAY >= to) return []; + if (from !== undefined && (to - from) / DAY <= MAX_LISTED_DAYS) { const days: string[] = []; for (let day = Math.floor(from / DAY) * DAY; day < to; day += DAY) days.push(isoDay(day));