Skip to content

Commit ec2f93f

Browse files
committed
Run special hydration tests
1 parent e5fcc65 commit ec2f93f

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

packages/query-core/src/hydration.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@ export function hydrate(
217217
let query = queryCache.get(queryHash)
218218
const existingQueryIsPending = query?.state.status === 'pending'
219219
const existingQueryIsFetching = query?.state.fetchStatus === 'fetching'
220+
const existingQueryIsUndefinedOrIsIdleUseQuery =
221+
!query ||
222+
(query.state.dataUpdatedAt === 0 &&
223+
query.state.status === 'pending' &&
224+
query.state.fetchStatus === 'idle')
220225

221226
// Do not hydrate if an existing query exists with newer data
222227
if (query) {
@@ -262,8 +267,8 @@ export function hydrate(
262267

263268
if (
264269
promise &&
265-
!existingQueryIsPending &&
266-
!existingQueryIsFetching &&
270+
(existingQueryIsUndefinedOrIsIdleUseQuery ||
271+
(!existingQueryIsPending && !existingQueryIsFetching)) &&
267272
// Only hydrate if dehydration is newer than any existing data,
268273
// this is always true for new queries
269274
(dehydratedAt === undefined || dehydratedAt > query.state.dataUpdatedAt)

packages/react-query/src/HydrationBoundary.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,15 @@ export const HydrationBoundary = ({
6868
const existingQueries: DehydratedState['queries'] = []
6969
for (const dehydratedQuery of queries) {
7070
const existingQuery = queryCache.get(dehydratedQuery.queryHash)
71+
const existingQueryIsIdleUseQuery =
72+
existingQuery?.state.dataUpdatedAt === 0 &&
73+
existingQuery.state.status === 'pending' &&
74+
existingQuery.state.fetchStatus === 'idle'
7175

7276
if (!existingQuery) {
7377
newQueries.push(dehydratedQuery)
78+
} else if (existingQueryIsIdleUseQuery) {
79+
newQueries.push(dehydratedQuery)
7480
} else {
7581
const hydrationIsNewer =
7682
dehydratedQuery.state.dataUpdatedAt >

packages/react-query/src/__tests__/HydrationBoundary.test.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import {
77
HydrationBoundary,
88
QueryClient,
99
QueryClientProvider,
10+
defaultShouldDehydrateQuery,
1011
dehydrate,
1112
useQuery,
13+
useSuspenseQuery,
1214
} from '..'
1315
import type { hydrate } from '@tanstack/query-core'
1416

@@ -481,6 +483,70 @@ describe('React hydration', () => {
481483
clientQueryClient.clear()
482484
})
483485

486+
test('should hydrate pending idle queries in render to avoid suspense refetches', async () => {
487+
const queryKey = ['string'] as const
488+
489+
const makeQueryClient = () =>
490+
new QueryClient({
491+
defaultOptions: {
492+
dehydrate: {
493+
shouldDehydrateQuery: (query) =>
494+
defaultShouldDehydrateQuery(query) ||
495+
query.state.status === 'pending',
496+
shouldRedactErrors: () => false,
497+
},
498+
},
499+
})
500+
501+
const prefetchClient = makeQueryClient()
502+
void prefetchClient.prefetchQuery({
503+
queryKey,
504+
queryFn: () => Promise.resolve(['stringCached']),
505+
staleTime: Infinity,
506+
})
507+
const dehydratedState = dehydrate(prefetchClient)
508+
509+
const queryFn = vi.fn(() => Promise.resolve(['string']))
510+
const suspenseQueryFn = vi.fn(() => Promise.resolve(['string']))
511+
const queryClient = new QueryClient()
512+
513+
function Header() {
514+
useQuery({
515+
queryKey,
516+
queryFn,
517+
})
518+
return null
519+
}
520+
521+
function Page() {
522+
const { data } = useSuspenseQuery({
523+
queryKey,
524+
queryFn: suspenseQueryFn,
525+
})
526+
return <div>{data}</div>
527+
}
528+
529+
render(
530+
<QueryClientProvider client={queryClient}>
531+
<Header />
532+
<HydrationBoundary state={dehydratedState}>
533+
<React.Suspense fallback="loading">
534+
<Page />
535+
</React.Suspense>
536+
</HydrationBoundary>
537+
</QueryClientProvider>,
538+
)
539+
540+
await Promise.resolve()
541+
await Promise.resolve()
542+
await Promise.resolve()
543+
await vi.advanceTimersByTimeAsync(1)
544+
expect(queryClient.getQueryData(queryKey)).toEqual(['stringCached'])
545+
expect(suspenseQueryFn).toHaveBeenCalledTimes(0)
546+
547+
queryClient.clear()
548+
})
549+
484550
test('should not refetch when query has enabled set to false', async () => {
485551
const queryFn = vi.fn()
486552
const queryClient = new QueryClient()

0 commit comments

Comments
 (0)