From a604a6a623ff872df9b65e79c3bd310471598550 Mon Sep 17 00:00:00 2001 From: ColemanDunn <42652642+ColemanDunn@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:40:40 -0700 Subject: [PATCH 1/3] prevent registered useQueries from skipping hydration --- packages/query-core/src/hydration.ts | 9 ++- .../react-query/src/HydrationBoundary.tsx | 6 ++ .../src/__tests__/HydrationBoundary.test.tsx | 66 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index c75d8ee332c..4eb3b50be59 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -217,6 +217,11 @@ export function hydrate( let query = queryCache.get(queryHash) const existingQueryIsPending = query?.state.status === 'pending' const existingQueryIsFetching = query?.state.fetchStatus === 'fetching' + const existingQueryIsUndefinedOrIsIdleUseQuery = + !query || + (query.state.dataUpdatedAt === 0 && + query.state.status === 'pending' && + query.state.fetchStatus === 'idle') // Do not hydrate if an existing query exists with newer data if (query) { @@ -262,8 +267,8 @@ export function hydrate( if ( promise && - !existingQueryIsPending && - !existingQueryIsFetching && + (existingQueryIsUndefinedOrIsIdleUseQuery || + (!existingQueryIsPending && !existingQueryIsFetching)) && // Only hydrate if dehydration is newer than any existing data, // this is always true for new queries (dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt) diff --git a/packages/react-query/src/HydrationBoundary.tsx b/packages/react-query/src/HydrationBoundary.tsx index 901c8e9686c..ec3590f9635 100644 --- a/packages/react-query/src/HydrationBoundary.tsx +++ b/packages/react-query/src/HydrationBoundary.tsx @@ -68,9 +68,15 @@ export const HydrationBoundary = ({ const existingQueries: DehydratedState['queries'] = [] for (const dehydratedQuery of queries) { const existingQuery = queryCache.get(dehydratedQuery.queryHash) + const existingQueryIsIdleUseQuery = + existingQuery?.state.dataUpdatedAt === 0 && + existingQuery.state.status === 'pending' && + existingQuery.state.fetchStatus === 'idle' if (!existingQuery) { newQueries.push(dehydratedQuery) + } else if (existingQueryIsIdleUseQuery) { + newQueries.push(dehydratedQuery) } else { const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > diff --git a/packages/react-query/src/__tests__/HydrationBoundary.test.tsx b/packages/react-query/src/__tests__/HydrationBoundary.test.tsx index 67b409c6ad1..5794b1fdb43 100644 --- a/packages/react-query/src/__tests__/HydrationBoundary.test.tsx +++ b/packages/react-query/src/__tests__/HydrationBoundary.test.tsx @@ -7,8 +7,10 @@ import { HydrationBoundary, QueryClient, QueryClientProvider, + defaultShouldDehydrateQuery, dehydrate, useQuery, + useSuspenseQuery, } from '..' import type { hydrate } from '@tanstack/query-core' @@ -481,6 +483,70 @@ describe('React hydration', () => { clientQueryClient.clear() }) + test('should hydrate pending idle queries in render to avoid suspense refetches', async () => { + const queryKey = ['string'] as const + + const makeQueryClient = () => + new QueryClient({ + defaultOptions: { + dehydrate: { + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || + query.state.status === 'pending', + shouldRedactErrors: () => false, + }, + }, + }) + + const prefetchClient = makeQueryClient() + void prefetchClient.prefetchQuery({ + queryKey, + queryFn: () => Promise.resolve(['stringCached']), + staleTime: Infinity, + }) + const dehydratedState = dehydrate(prefetchClient) + + const queryFn = vi.fn(() => Promise.resolve(['string'])) + const suspenseQueryFn = vi.fn(() => Promise.resolve(['string'])) + const queryClient = new QueryClient() + + function Header() { + useQuery({ + queryKey, + queryFn, + }) + return null + } + + function Page() { + const { data } = useSuspenseQuery({ + queryKey, + queryFn: suspenseQueryFn, + }) + return