From 050c9d5bf61c94c5eb9367fe0e7b239f77f1f537 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 17:08:50 +0100 Subject: [PATCH 01/13] add failing repro test --- .../src/__tests__/hydration.test.tsx | 53 +++++++++++++++++++ packages/query-core/src/hydration.ts | 1 + 2 files changed, 54 insertions(+) diff --git a/packages/query-core/src/__tests__/hydration.test.tsx b/packages/query-core/src/__tests__/hydration.test.tsx index 182a46b57b..00570a98b0 100644 --- a/packages/query-core/src/__tests__/hydration.test.tsx +++ b/packages/query-core/src/__tests__/hydration.test.tsx @@ -1066,4 +1066,57 @@ describe('dehydration and rehydration', () => { clientQueryClient.clear() serverQueryClient.clear() }) + + test('should overwrite data when a new promise is streamed in', async () => { + const countRef = { current: 0 } + // --- server --- + const serverQueryClient = createQueryClient({ + defaultOptions: { + dehydrate: { + shouldDehydrateQuery: () => true, + }, + }, + }) + + const query = { + queryKey: ['data'], + queryFn: async () => { + await sleep(10) + return countRef.current + }, + } + + const promise = serverQueryClient.prefetchQuery(query) + + const dehydrated = dehydrate(serverQueryClient) + + // --- client --- + + const clientQueryClient = createQueryClient() + + hydrate(clientQueryClient, dehydrated) + + await promise + await waitFor(() => + expect(clientQueryClient.getQueryData(['data'])).toBe(0), + ) + + // --- server --- + countRef.current++ + const promise2 = serverQueryClient.prefetchQuery(query) + + const dehydrated2 = dehydrate(serverQueryClient) + + // --- client --- + + hydrate(clientQueryClient, dehydrated2) + + await promise2 + await waitFor(() => + expect(clientQueryClient.getQueryData(['data'])).toBe(1), + ) + + clientQueryClient.clear() + serverQueryClient.clear() + }) }) diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index a3dfd0e482..94b06ad935 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -179,6 +179,7 @@ export function hydrate( // Do not hydrate if an existing query exists with newer data if (query) { + console.log('query', query) if (query.state.dataUpdatedAt < state.dataUpdatedAt) { // omit fetchStatus from dehydrated state // so that query stays in its current fetchStatus From 524f5ff487e6114ae3c2cbbbd14cea7acf35e5cf Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 17:17:12 +0100 Subject: [PATCH 02/13] update assertinos --- .../src/__tests__/hydration.test.tsx | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/query-core/src/__tests__/hydration.test.tsx b/packages/query-core/src/__tests__/hydration.test.tsx index 00570a98b0..8aad404aab 100644 --- a/packages/query-core/src/__tests__/hydration.test.tsx +++ b/packages/query-core/src/__tests__/hydration.test.tsx @@ -1067,13 +1067,15 @@ describe('dehydration and rehydration', () => { serverQueryClient.clear() }) - test('should overwrite data when a new promise is streamed in', async () => { + test.only('should overwrite data when a new promise is streamed in', async () => { + const serializeDataMock = vi.fn((data: any) => data) const countRef = { current: 0 } // --- server --- const serverQueryClient = createQueryClient({ defaultOptions: { dehydrate: { shouldDehydrateQuery: () => true, + serializeData: serializeDataMock, }, }, }) @@ -1082,6 +1084,7 @@ describe('dehydration and rehydration', () => { queryKey: ['data'], queryFn: async () => { await sleep(10) + console.log('queryFn', countRef.current) return countRef.current }, } @@ -1091,16 +1094,30 @@ describe('dehydration and rehydration', () => { const dehydrated = dehydrate(serverQueryClient) // --- client --- - - const clientQueryClient = createQueryClient() + const deserializeDataMock = vi.fn((data: any) => data) + const clientQueryClient = createQueryClient({ + defaultOptions: { + hydrate: { + deserializeData: deserializeDataMock, + }, + }, + }) hydrate(clientQueryClient, dehydrated) await promise await waitFor(() => - expect(clientQueryClient.getQueryData(['data'])).toBe(0), + expect(clientQueryClient.getQueryData(query.queryKey)).toBe(0), ) + console.log('serialize mock', serializeDataMock.mock.calls) + + expect(serializeDataMock).toHaveBeenCalledTimes(1) + expect(serializeDataMock).toHaveBeenCalledWith(0) + + expect(deserializeDataMock).toHaveBeenCalledTimes(1) + expect(deserializeDataMock).toHaveBeenCalledWith(0) + // --- server --- countRef.current++ const promise2 = serverQueryClient.prefetchQuery(query) @@ -1112,9 +1129,17 @@ describe('dehydration and rehydration', () => { hydrate(clientQueryClient, dehydrated2) await promise2 - await waitFor(() => - expect(clientQueryClient.getQueryData(['data'])).toBe(1), - ) + // await waitFor(() => + // expect(clientQueryClient.getQueryData(query.queryKey)).toBe(1), + // ) + + console.log('serialize mock', serializeDataMock.mock.calls) + + expect(serializeDataMock).toHaveBeenCalledTimes(2) + expect(serializeDataMock).toHaveBeenCalledWith(1) + + expect(deserializeDataMock).toHaveBeenCalledTimes(2) + expect(deserializeDataMock).toHaveBeenCalledWith(1) clientQueryClient.clear() serverQueryClient.clear() From cf37452e72e44609e49600f8b7c124cc5a197af5 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 17:20:57 +0100 Subject: [PATCH 03/13] add logg --- packages/query-core/src/hydration.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index 94b06ad935..96fde3f4c0 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -177,9 +177,11 @@ export function hydrate( const data = state.data === undefined ? state.data : deserializeData(state.data) + console.log('data', data) + console.log('query', query) + // Do not hydrate if an existing query exists with newer data if (query) { - console.log('query', query) if (query.state.dataUpdatedAt < state.dataUpdatedAt) { // omit fetchStatus from dehydrated state // so that query stays in its current fetchStatus From d82cfb20a4ec1b44274e12fd03871b8ec239e067 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 17:34:10 +0100 Subject: [PATCH 04/13] ehm - maybe fix? --- .../src/__tests__/hydration.test.tsx | 19 +++++++++++-------- packages/query-core/src/hydration.ts | 8 +++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/query-core/src/__tests__/hydration.test.tsx b/packages/query-core/src/__tests__/hydration.test.tsx index 8aad404aab..b59ac57f40 100644 --- a/packages/query-core/src/__tests__/hydration.test.tsx +++ b/packages/query-core/src/__tests__/hydration.test.tsx @@ -1091,7 +1091,7 @@ describe('dehydration and rehydration', () => { const promise = serverQueryClient.prefetchQuery(query) - const dehydrated = dehydrate(serverQueryClient) + let dehydrated = dehydrate(serverQueryClient) // --- client --- const deserializeDataMock = vi.fn((data: any) => data) @@ -1122,23 +1122,26 @@ describe('dehydration and rehydration', () => { countRef.current++ const promise2 = serverQueryClient.prefetchQuery(query) - const dehydrated2 = dehydrate(serverQueryClient) + dehydrated = dehydrate(serverQueryClient) // --- client --- - hydrate(clientQueryClient, dehydrated2) + hydrate(clientQueryClient, dehydrated) await promise2 - // await waitFor(() => - // expect(clientQueryClient.getQueryData(query.queryKey)).toBe(1), - // ) + await waitFor(() => + expect(clientQueryClient.getQueryData(query.queryKey)).toBe(1), + ) console.log('serialize mock', serializeDataMock.mock.calls) - expect(serializeDataMock).toHaveBeenCalledTimes(2) + // Why are we getting 3 calls here? Should be 2? + // expect(serializeDataMock).toHaveBeenCalledTimes(2) + expect(serializeDataMock).toHaveBeenCalledTimes(3) expect(serializeDataMock).toHaveBeenCalledWith(1) - expect(deserializeDataMock).toHaveBeenCalledTimes(2) + // expect(deserializeDataMock).toHaveBeenCalledTimes(2) + expect(deserializeDataMock).toHaveBeenCalledTimes(3) expect(deserializeDataMock).toHaveBeenCalledWith(1) clientQueryClient.clear() diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index 96fde3f4c0..dfb719dee3 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -80,7 +80,8 @@ function dehydrateQuery( }, queryKey: query.queryKey, queryHash: query.queryHash, - ...(query.state.status === 'pending' && { + ...((query.state.status === 'pending' || + query.state.fetchStatus === 'fetching') && { promise: query.promise?.then(serializeData).catch((error) => { if (process.env.NODE_ENV !== 'production') { console.error( @@ -143,6 +144,7 @@ export function hydrate( dehydratedState: unknown, options?: HydrateOptions, ): void { + console.log('[hydrate] dehydratedState', dehydratedState) if (typeof dehydratedState !== 'object' || dehydratedState === null) { return } @@ -177,8 +179,8 @@ export function hydrate( const data = state.data === undefined ? state.data : deserializeData(state.data) - console.log('data', data) - console.log('query', query) + console.log('[hydrate] data', data) + console.log('[hydrate] query', query) // Do not hydrate if an existing query exists with newer data if (query) { From 6eaf6fc018246ce06917c6e812fd08f3307a3b35 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 18:11:40 +0100 Subject: [PATCH 05/13] rm -only --- packages/query-core/src/__tests__/hydration.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/query-core/src/__tests__/hydration.test.tsx b/packages/query-core/src/__tests__/hydration.test.tsx index b59ac57f40..eee5b5e0a3 100644 --- a/packages/query-core/src/__tests__/hydration.test.tsx +++ b/packages/query-core/src/__tests__/hydration.test.tsx @@ -1067,7 +1067,7 @@ describe('dehydration and rehydration', () => { serverQueryClient.clear() }) - test.only('should overwrite data when a new promise is streamed in', async () => { + test('should overwrite data when a new promise is streamed in', async () => { const serializeDataMock = vi.fn((data: any) => data) const countRef = { current: 0 } // --- server --- From 8c7ccf232be9f3f58d6a61e76b38285b2b4acc54 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 19:18:19 +0100 Subject: [PATCH 06/13] make example --- integrations/react-next-15/app/_action.ts | 11 +++++++++++ .../react-next-15/app/client-component.tsx | 10 ++++++++-- integrations/react-next-15/app/count/route.ts | 5 +++++ .../react-next-15/app/make-query-client.ts | 14 ++++++++++++-- integrations/react-next-15/app/page.tsx | 16 +++++++++++++--- .../query-core/src/__tests__/hydration.test.tsx | 4 +++- 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 integrations/react-next-15/app/_action.ts create mode 100644 integrations/react-next-15/app/count/route.ts diff --git a/integrations/react-next-15/app/_action.ts b/integrations/react-next-15/app/_action.ts new file mode 100644 index 0000000000..5930be2e08 --- /dev/null +++ b/integrations/react-next-15/app/_action.ts @@ -0,0 +1,11 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { countRef } from './make-query-client' + +export async function queryExampleAction() { + await Promise.resolve() + countRef.current++ + revalidatePath('/', 'page') + return undefined +} diff --git a/integrations/react-next-15/app/client-component.tsx b/integrations/react-next-15/app/client-component.tsx index 29dd7b33c9..bd1bfbe407 100644 --- a/integrations/react-next-15/app/client-component.tsx +++ b/integrations/react-next-15/app/client-component.tsx @@ -8,10 +8,16 @@ export function ClientComponent() { const query = useQuery({ queryKey: ['data'], queryFn: async () => { - await new Promise((r) => setTimeout(r, 1000)) + const { count } = await ( + await fetch('http://localhost:3000/count') + ).json() + + console.log('client', count) + return { text: 'data from client', date: Temporal.PlainDate.from('2023-01-01'), + count, } }, }) @@ -26,7 +32,7 @@ export function ClientComponent() { return (
- {query.data.text} - {query.data.date.toJSON()} + {query.data.text} - {query.data.date.toJSON()} - {query.data.count}
) } diff --git a/integrations/react-next-15/app/count/route.ts b/integrations/react-next-15/app/count/route.ts new file mode 100644 index 0000000000..f56c243ad9 --- /dev/null +++ b/integrations/react-next-15/app/count/route.ts @@ -0,0 +1,5 @@ +import { countRef } from '../make-query-client' + +export const GET = () => { + return Response.json({ count: countRef.current }) +} diff --git a/integrations/react-next-15/app/make-query-client.ts b/integrations/react-next-15/app/make-query-client.ts index 3d0ff40cb8..f7f731271c 100644 --- a/integrations/react-next-15/app/make-query-client.ts +++ b/integrations/react-next-15/app/make-query-client.ts @@ -10,6 +10,10 @@ const plainDate = { test: (v) => v instanceof Temporal.PlainDate, } satisfies TsonType +export const countRef = { + current: 0, +} + export const tson = createTson({ types: [plainDate], }) @@ -22,13 +26,19 @@ export function makeQueryClient() { * Called when the query is rebuilt from a prefetched * promise, before the query data is put into the cache. */ - deserializeData: tson.deserialize, + deserializeData: (data) => { + console.log('deserializeData', data) + return tson.deserialize(data) + }, }, queries: { staleTime: 60 * 1000, }, dehydrate: { - serializeData: tson.serialize, + serializeData: (data) => { + console.log('serializeData', data) + return tson.serialize(data) + }, shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === 'pending', diff --git a/integrations/react-next-15/app/page.tsx b/integrations/react-next-15/app/page.tsx index 2382ab540f..9002990a56 100644 --- a/integrations/react-next-15/app/page.tsx +++ b/integrations/react-next-15/app/page.tsx @@ -2,20 +2,27 @@ import React from 'react' import { HydrationBoundary, dehydrate } from '@tanstack/react-query' import { Temporal } from '@js-temporal/polyfill' import { ClientComponent } from './client-component' -import { makeQueryClient, tson } from './make-query-client' +import { makeQueryClient } from './make-query-client' +import { queryExampleAction } from './_action' const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) -export default async function Home() { +export default function Home() { const queryClient = makeQueryClient() void queryClient.prefetchQuery({ queryKey: ['data'], queryFn: async () => { - await sleep(2000) + const { count } = await ( + await fetch('http://localhost:3000/count') + ).json() + + console.log('server', count) + return { text: 'data from server', date: Temporal.PlainDate.from('2024-01-01'), + count, } }, }) @@ -25,6 +32,9 @@ export default async function Home() { +
+ +
) } diff --git a/packages/query-core/src/__tests__/hydration.test.tsx b/packages/query-core/src/__tests__/hydration.test.tsx index eee5b5e0a3..ad16706575 100644 --- a/packages/query-core/src/__tests__/hydration.test.tsx +++ b/packages/query-core/src/__tests__/hydration.test.tsx @@ -1069,6 +1069,8 @@ describe('dehydration and rehydration', () => { test('should overwrite data when a new promise is streamed in', async () => { const serializeDataMock = vi.fn((data: any) => data) + const deserializeDataMock = vi.fn((data: any) => data) + const countRef = { current: 0 } // --- server --- const serverQueryClient = createQueryClient({ @@ -1094,7 +1096,7 @@ describe('dehydration and rehydration', () => { let dehydrated = dehydrate(serverQueryClient) // --- client --- - const deserializeDataMock = vi.fn((data: any) => data) + const clientQueryClient = createQueryClient({ defaultOptions: { hydrate: { From 4efa64200d7909eb1ec5d4997564d3962675f035 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 19:19:39 +0100 Subject: [PATCH 07/13] upd --- integrations/react-next-15/app/make-query-client.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/integrations/react-next-15/app/make-query-client.ts b/integrations/react-next-15/app/make-query-client.ts index f7f731271c..7761109173 100644 --- a/integrations/react-next-15/app/make-query-client.ts +++ b/integrations/react-next-15/app/make-query-client.ts @@ -39,9 +39,15 @@ export function makeQueryClient() { console.log('serializeData', data) return tson.serialize(data) }, - shouldDehydrateQuery: (query) => - defaultShouldDehydrateQuery(query) || - query.state.status === 'pending', + shouldDehydrateQuery: (query) => { + const shouldDehydrate = + defaultShouldDehydrateQuery(query) || + query.state.status === 'pending' + + console.log('shouldDehydrateQuery', query.queryKey, shouldDehydrate) + + return shouldDehydrate + }, }, }, }) From f32f6e3a50c82d186321fcc5fd3b71d5ca5d9da0 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 19:30:55 +0100 Subject: [PATCH 08/13] ad debug logs --- packages/query-core/src/hydration.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index dfb719dee3..f7d824a1f4 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -129,14 +129,16 @@ export function dehydrate( client.getDefaultOptions().dehydrate?.serializeData ?? defaultTransformerFn - const queries = client - .getQueryCache() - .getAll() - .flatMap((query) => - filterQuery(query) ? [dehydrateQuery(query, serializeData)] : [], - ) + const queries = client.getQueryCache().getAll() + + const filteredQueries = queries.flatMap((query) => + filterQuery(query) ? [dehydrateQuery(query, serializeData)] : [], + ) + + console.log('[dehydrate] queries', queries) + console.log('[dehydrate] filteredQueries', filteredQueries) - return { mutations, queries } + return { mutations, queries: filteredQueries } } export function hydrate( From 77362c42366bb2fb526ee5f5166893f8d528b8c3 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Mon, 2 Dec 2024 20:01:06 +0100 Subject: [PATCH 09/13] more debugging --- integrations/react-next-15/app/page.tsx | 6 +++++- packages/query-core/src/hydration.ts | 3 +-- packages/react-query/src/HydrationBoundary.tsx | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/integrations/react-next-15/app/page.tsx b/integrations/react-next-15/app/page.tsx index 9002990a56..fb5ee34b44 100644 --- a/integrations/react-next-15/app/page.tsx +++ b/integrations/react-next-15/app/page.tsx @@ -27,9 +27,13 @@ export default function Home() { }, }) + const state = dehydrate(queryClient) + + console.log('state', state) + return (
- +
diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index f7d824a1f4..2c01aead39 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -80,8 +80,7 @@ function dehydrateQuery( }, queryKey: query.queryKey, queryHash: query.queryHash, - ...((query.state.status === 'pending' || - query.state.fetchStatus === 'fetching') && { + ...(query.state.fetchStatus === 'fetching' && { promise: query.promise?.then(serializeData).catch((error) => { if (process.env.NODE_ENV !== 'production') { console.error( diff --git a/packages/react-query/src/HydrationBoundary.tsx b/packages/react-query/src/HydrationBoundary.tsx index 407933fc5c..2ffb529cb4 100644 --- a/packages/react-query/src/HydrationBoundary.tsx +++ b/packages/react-query/src/HydrationBoundary.tsx @@ -68,12 +68,22 @@ export const HydrationBoundary = ({ for (const dehydratedQuery of queries) { const existingQuery = queryCache.get(dehydratedQuery.queryHash) + console.log('existingQuery', existingQuery) if (!existingQuery) { newQueries.push(dehydratedQuery) } else { const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > existingQuery.state.dataUpdatedAt + + console.log( + 'hydrationIsNewer', + dehydratedQuery.queryKey, + hydrationIsNewer, + dehydratedQuery.state.dataUpdatedAt, + existingQuery.state.dataUpdatedAt, + ) + const queryAlreadyQueued = hydrationQueue?.find( (query) => query.queryHash === dehydratedQuery.queryHash, ) @@ -89,6 +99,10 @@ export const HydrationBoundary = ({ } } + console.log('queries', queries) + console.log('newQueries', newQueries) + console.log('existingQueries', existingQueries) + if (newQueries.length > 0) { // It's actually fine to call this with queries/state that already exists // in the cache, or is older. hydrate() is idempotent for queries. From 5fa1a33f66cab132fd989c07bd1e4587333f7741 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Tue, 3 Dec 2024 16:15:02 +0100 Subject: [PATCH 10/13] push --- .../react-next-15/app/make-query-client.ts | 2 -- integrations/react-next-15/app/page.tsx | 2 -- integrations/react-next-15/app/providers.tsx | 16 +++++++++---- packages/query-core/package.json | 3 ++- packages/query-core/src/hydration.ts | 23 +++++++++---------- packages/query-core/src/query.ts | 2 ++ packages/react-query/package.json | 1 + .../react-query/src/HydrationBoundary.tsx | 23 +++++++++++-------- 8 files changed, 42 insertions(+), 30 deletions(-) diff --git a/integrations/react-next-15/app/make-query-client.ts b/integrations/react-next-15/app/make-query-client.ts index 7761109173..05d91e9249 100644 --- a/integrations/react-next-15/app/make-query-client.ts +++ b/integrations/react-next-15/app/make-query-client.ts @@ -27,7 +27,6 @@ export function makeQueryClient() { * promise, before the query data is put into the cache. */ deserializeData: (data) => { - console.log('deserializeData', data) return tson.deserialize(data) }, }, @@ -36,7 +35,6 @@ export function makeQueryClient() { }, dehydrate: { serializeData: (data) => { - console.log('serializeData', data) return tson.serialize(data) }, shouldDehydrateQuery: (query) => { diff --git a/integrations/react-next-15/app/page.tsx b/integrations/react-next-15/app/page.tsx index fb5ee34b44..be72e87adb 100644 --- a/integrations/react-next-15/app/page.tsx +++ b/integrations/react-next-15/app/page.tsx @@ -29,8 +29,6 @@ export default function Home() { const state = dehydrate(queryClient) - console.log('state', state) - return (
diff --git a/integrations/react-next-15/app/providers.tsx b/integrations/react-next-15/app/providers.tsx index 25a9217ff9..96d4c546cf 100644 --- a/integrations/react-next-15/app/providers.tsx +++ b/integrations/react-next-15/app/providers.tsx @@ -1,16 +1,24 @@ 'use client' -import { QueryClientProvider } from '@tanstack/react-query' -import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +// import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import * as React from 'react' import { makeQueryClient } from '@/app/make-query-client' +let queryClientSingleton: QueryClient | undefined +const getQueryClientSingleton = () => { + if (typeof window !== 'undefined') { + return makeQueryClient() + } + return (queryClientSingleton ??= makeQueryClient()) +} export default function Providers({ children }: { children: React.ReactNode }) { - const [queryClient] = React.useState(() => makeQueryClient()) + // const [queryClient] = React.useState(() => makeQueryClient()) + const queryClient = getQueryClientSingleton() return ( {children} - + {/* */} ) } diff --git a/packages/query-core/package.json b/packages/query-core/package.json index 208a991f9d..d87618c7a1 100644 --- a/packages/query-core/package.json +++ b/packages/query-core/package.json @@ -29,7 +29,8 @@ "test:lib": "vitest", "test:lib:dev": "pnpm run test:lib --watch", "test:build": "publint --strict && attw --pack", - "build": "tsup" + "build": "tsup", + "build:dev": "tsup --watch" }, "type": "module", "types": "build/legacy/index.d.ts", diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index 2c01aead39..d3b261b582 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -77,6 +77,9 @@ function dehydrateQuery( ...(query.state.data !== undefined && { data: serializeData(query.state.data), }), + ...(query.state.fetchStatus === 'fetching' && { + promiseDehydratedAt: Date.now(), + }), }, queryKey: query.queryKey, queryHash: query.queryHash, @@ -128,16 +131,14 @@ export function dehydrate( client.getDefaultOptions().dehydrate?.serializeData ?? defaultTransformerFn - const queries = client.getQueryCache().getAll() - - const filteredQueries = queries.flatMap((query) => - filterQuery(query) ? [dehydrateQuery(query, serializeData)] : [], - ) - - console.log('[dehydrate] queries', queries) - console.log('[dehydrate] filteredQueries', filteredQueries) + const queries = client + .getQueryCache() + .getAll() + .flatMap((query) => + filterQuery(query) ? [dehydrateQuery(query, serializeData)] : [], + ) - return { mutations, queries: filteredQueries } + return { mutations, queries } } export function hydrate( @@ -145,7 +146,6 @@ export function hydrate( dehydratedState: unknown, options?: HydrateOptions, ): void { - console.log('[hydrate] dehydratedState', dehydratedState) if (typeof dehydratedState !== 'object' || dehydratedState === null) { return } @@ -180,8 +180,7 @@ export function hydrate( const data = state.data === undefined ? state.data : deserializeData(state.data) - console.log('[hydrate] data', data) - console.log('[hydrate] query', query) + console.log('[hydrate] query', query?.state) // Do not hydrate if an existing query exists with newer data if (query) { diff --git a/packages/query-core/src/query.ts b/packages/query-core/src/query.ts index 3485d8ce28..eb5453b19c 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -56,6 +56,7 @@ export interface QueryState { isInvalidated: boolean status: QueryStatus fetchStatus: FetchStatus + promiseDehydratedAt: number } export interface FetchContext< @@ -679,5 +680,6 @@ function getDefaultState< isInvalidated: false, status: hasData ? 'success' : 'pending', fetchStatus: 'idle', + promiseDehydratedAt: 0, } } diff --git a/packages/react-query/package.json b/packages/react-query/package.json index c0ce2f14bd..5953a9c00a 100644 --- a/packages/react-query/package.json +++ b/packages/react-query/package.json @@ -31,6 +31,7 @@ "test:build": "publint --strict && attw --pack", "build": "pnpm build:tsup && pnpm build:codemods", "build:tsup": "tsup", + "build:tsup:dev": "tsup --watch", "build:codemods": "cpy ../query-codemods/* ./build/codemods" }, "type": "module", diff --git a/packages/react-query/src/HydrationBoundary.tsx b/packages/react-query/src/HydrationBoundary.tsx index 2ffb529cb4..cdf33e20ef 100644 --- a/packages/react-query/src/HydrationBoundary.tsx +++ b/packages/react-query/src/HydrationBoundary.tsx @@ -68,20 +68,29 @@ export const HydrationBoundary = ({ for (const dehydratedQuery of queries) { const existingQuery = queryCache.get(dehydratedQuery.queryHash) - console.log('existingQuery', existingQuery) + console.log( + '[HydrationBoundary] existingQuery', + JSON.stringify(existingQuery?.state, null, 2), + ) + console.log( + '[HydrationBoundary] dehydratedQuery', + JSON.stringify(dehydratedQuery.state, null, 2), + ) if (!existingQuery) { newQueries.push(dehydratedQuery) } else { const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > - existingQuery.state.dataUpdatedAt + existingQuery.state.dataUpdatedAt || + dehydratedQuery.state.promiseDehydratedAt > + existingQuery.state.promiseDehydratedAt console.log( - 'hydrationIsNewer', + '[HydrationBoundary] hydrationIsNewer', dehydratedQuery.queryKey, hydrationIsNewer, - dehydratedQuery.state.dataUpdatedAt, - existingQuery.state.dataUpdatedAt, + dehydratedQuery.state.promiseDehydratedAt, + existingQuery.state.promiseDehydratedAt, ) const queryAlreadyQueued = hydrationQueue?.find( @@ -99,10 +108,6 @@ export const HydrationBoundary = ({ } } - console.log('queries', queries) - console.log('newQueries', newQueries) - console.log('existingQueries', existingQueries) - if (newQueries.length > 0) { // It's actually fine to call this with queries/state that already exists // in the cache, or is older. hydrate() is idempotent for queries. From 7eec72de610335df2a42d17f9c49ea77df3bb091 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Tue, 3 Dec 2024 16:34:55 +0100 Subject: [PATCH 11/13] maybe??? --- integrations/react-next-15/app/page.tsx | 2 ++ integrations/react-next-15/app/providers.tsx | 2 +- packages/query-core/src/hydration.ts | 3 +- .../react-query/src/HydrationBoundary.tsx | 34 +++++++++++++------ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/integrations/react-next-15/app/page.tsx b/integrations/react-next-15/app/page.tsx index be72e87adb..65e7e9b728 100644 --- a/integrations/react-next-15/app/page.tsx +++ b/integrations/react-next-15/app/page.tsx @@ -29,6 +29,8 @@ export default function Home() { const state = dehydrate(queryClient) + console.log('[page] state', state) + return (
diff --git a/integrations/react-next-15/app/providers.tsx b/integrations/react-next-15/app/providers.tsx index 96d4c546cf..4a7b44500b 100644 --- a/integrations/react-next-15/app/providers.tsx +++ b/integrations/react-next-15/app/providers.tsx @@ -6,7 +6,7 @@ import { makeQueryClient } from '@/app/make-query-client' let queryClientSingleton: QueryClient | undefined const getQueryClientSingleton = () => { - if (typeof window !== 'undefined') { + if (typeof window === 'undefined') { return makeQueryClient() } return (queryClientSingleton ??= makeQueryClient()) diff --git a/packages/query-core/src/hydration.ts b/packages/query-core/src/hydration.ts index d3b261b582..6182b152bf 100644 --- a/packages/query-core/src/hydration.ts +++ b/packages/query-core/src/hydration.ts @@ -71,6 +71,7 @@ function dehydrateQuery( query: Query, serializeData: TransformerFn, ): DehydratedQuery { + // console.log('[dehydrateQuery] query', query.queryKey) return { state: { ...query.state, @@ -180,7 +181,7 @@ export function hydrate( const data = state.data === undefined ? state.data : deserializeData(state.data) - console.log('[hydrate] query', query?.state) + // console.log('[hydrate] query', JSON.stringify(query?.state, null, 2)) // Do not hydrate if an existing query exists with newer data if (query) { diff --git a/packages/react-query/src/HydrationBoundary.tsx b/packages/react-query/src/HydrationBoundary.tsx index cdf33e20ef..6d67814bb9 100644 --- a/packages/react-query/src/HydrationBoundary.tsx +++ b/packages/react-query/src/HydrationBoundary.tsx @@ -35,6 +35,11 @@ export const HydrationBoundary = ({ DehydratedState['queries'] | undefined >() + // console.log( + // '[HydrationBoundary] rendering with state', + // JSON.stringify(state, null, 2), + // ) + const optionsRef = React.useRef(options) optionsRef.current = options @@ -68,22 +73,22 @@ export const HydrationBoundary = ({ for (const dehydratedQuery of queries) { const existingQuery = queryCache.get(dehydratedQuery.queryHash) - console.log( - '[HydrationBoundary] existingQuery', - JSON.stringify(existingQuery?.state, null, 2), - ) - console.log( - '[HydrationBoundary] dehydratedQuery', - JSON.stringify(dehydratedQuery.state, null, 2), - ) + // console.log( + // '[HydrationBoundary] existingQuery', + // JSON.stringify(existingQuery?.state, null, 2), + // ) + // console.log( + // '[HydrationBoundary] dehydratedQuery', + // JSON.stringify(dehydratedQuery.state, null, 2), + // ) if (!existingQuery) { newQueries.push(dehydratedQuery) } else { const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > existingQuery.state.dataUpdatedAt || - dehydratedQuery.state.promiseDehydratedAt > - existingQuery.state.promiseDehydratedAt + // @ts-expect-error + dehydratedQuery.promise?.status !== existingQuery.promise?.status console.log( '[HydrationBoundary] hydrationIsNewer', @@ -91,6 +96,8 @@ export const HydrationBoundary = ({ hydrationIsNewer, dehydratedQuery.state.promiseDehydratedAt, existingQuery.state.promiseDehydratedAt, + // @ts-expect-error + dehydratedQuery.promise?.status, ) const queryAlreadyQueued = hydrationQueue?.find( @@ -121,6 +128,13 @@ export const HydrationBoundary = ({ } }, [client, hydrationQueue, state]) + React.useEffect(() => { + console.log( + 'hydrationQueue changed', + JSON.stringify(hydrationQueue, null, 2), + ) + }, [hydrationQueue]) + React.useEffect(() => { if (hydrationQueue) { hydrate(client, { queries: hydrationQueue }, optionsRef.current) From edda7ba6a23c4720c4f3d4af75580d9422dfd55b Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Tue, 3 Dec 2024 16:36:53 +0100 Subject: [PATCH 12/13] rm log --- packages/react-query/src/HydrationBoundary.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/react-query/src/HydrationBoundary.tsx b/packages/react-query/src/HydrationBoundary.tsx index 6d67814bb9..5276472ad4 100644 --- a/packages/react-query/src/HydrationBoundary.tsx +++ b/packages/react-query/src/HydrationBoundary.tsx @@ -98,6 +98,8 @@ export const HydrationBoundary = ({ existingQuery.state.promiseDehydratedAt, // @ts-expect-error dehydratedQuery.promise?.status, + // @ts-expect-error + existingQuery.promise?.status, ) const queryAlreadyQueued = hydrationQueue?.find( @@ -128,13 +130,6 @@ export const HydrationBoundary = ({ } }, [client, hydrationQueue, state]) - React.useEffect(() => { - console.log( - 'hydrationQueue changed', - JSON.stringify(hydrationQueue, null, 2), - ) - }, [hydrationQueue]) - React.useEffect(() => { if (hydrationQueue) { hydrate(client, { queries: hydrationQueue }, optionsRef.current) From 85ee5bcf07cf00be96de39eb1f3db0c9acb48e23 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Tue, 3 Dec 2024 16:38:24 +0100 Subject: [PATCH 13/13] revert --- integrations/react-next-15/app/providers.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/integrations/react-next-15/app/providers.tsx b/integrations/react-next-15/app/providers.tsx index 4a7b44500b..eace7c7c61 100644 --- a/integrations/react-next-15/app/providers.tsx +++ b/integrations/react-next-15/app/providers.tsx @@ -1,19 +1,11 @@ 'use client' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { QueryClientProvider } from '@tanstack/react-query' // import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import * as React from 'react' import { makeQueryClient } from '@/app/make-query-client' -let queryClientSingleton: QueryClient | undefined -const getQueryClientSingleton = () => { - if (typeof window === 'undefined') { - return makeQueryClient() - } - return (queryClientSingleton ??= makeQueryClient()) -} export default function Providers({ children }: { children: React.ReactNode }) { - // const [queryClient] = React.useState(() => makeQueryClient()) - const queryClient = getQueryClientSingleton() + const [queryClient] = React.useState(() => makeQueryClient()) return (