From 76cd6a7719c1affec7f87765b56402834d2a6e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Thu, 29 Feb 2024 12:12:16 +0100 Subject: [PATCH 01/10] feat(fetchQuery): add 'prefetchWhenStale' option --- docs/reference/QueryClient.md | 16 ++++++++++++++++ packages/query-core/src/queryClient.ts | 11 ++++++++--- packages/query-core/src/types.ts | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index f2494c2ff7..22cc6b3755 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -93,6 +93,22 @@ try { } ``` +Add `prefetchWhenStale` when you want to let it behave just like useQuery, Fetch data when no data is cached, get data from cache while data is still in `gcTime` but let it prefetch new data while `staleTime` is over for the next consumer. + +```tsx +try { + const data = await queryClient.fetchQuery({ + queryKey, + queryFn, + staleTime: 10000, + gcTime: 50000, + prefetchWhenStale: true + }) +} catch (error) { + console.log(error) +} +``` + **Options** The options for `fetchQuery` are exactly the same as those of [`useQuery`](./framework/react/reference/useQuery), except the following: `enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, notifyOnChangeProps, throwOnError, select, suspense, placeholderData`; which are strictly for useQuery and useInfiniteQuery. You can check the [source code](https://github.com/TanStack/query/blob/7cd2d192e6da3df0b08e334ea1cf04cd70478827/packages/query-core/src/types.ts#L119) for more clarity. diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index ddb13c8de2..489d184aea 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -320,10 +320,15 @@ export class QueryClient { } const query = this.#queryCache.build(this, defaultedOptions) + const isStale = query.isStaleByTime(defaultedOptions.staleTime) - return query.isStaleByTime(defaultedOptions.staleTime) - ? query.fetch(defaultedOptions) - : Promise.resolve(query.state.data as TData) + if (isStale && query.state.data && options.prefetchWhenStale) { + query.fetch(defaultedOptions) + return Promise.resolve(query.state.data as TData) + } else if (isStale) { + return query.fetch(defaultedOptions) + } + return query.fetch(defaultedOptions) } prefetchQuery< diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index c4b2b29d4d..4c2987a12e 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -405,6 +405,7 @@ export interface FetchQueryOptions< * If the data is fresh it will be returned from the cache. */ staleTime?: number + prefetchWhenStale?: boolean } type FetchInfiniteQueryPages = From 9df8571c1ae98b5f3f3a01ab2d176d1e71233d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Mon, 4 Mar 2024 09:22:29 +0100 Subject: [PATCH 02/10] Revert "feat(fetchQuery): add 'prefetchWhenStale' option" This reverts commit 76cd6a7719c1affec7f87765b56402834d2a6e8b. --- docs/reference/QueryClient.md | 16 ---------------- packages/query-core/src/queryClient.ts | 11 +++-------- packages/query-core/src/types.ts | 1 - 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index 22cc6b3755..f2494c2ff7 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -93,22 +93,6 @@ try { } ``` -Add `prefetchWhenStale` when you want to let it behave just like useQuery, Fetch data when no data is cached, get data from cache while data is still in `gcTime` but let it prefetch new data while `staleTime` is over for the next consumer. - -```tsx -try { - const data = await queryClient.fetchQuery({ - queryKey, - queryFn, - staleTime: 10000, - gcTime: 50000, - prefetchWhenStale: true - }) -} catch (error) { - console.log(error) -} -``` - **Options** The options for `fetchQuery` are exactly the same as those of [`useQuery`](./framework/react/reference/useQuery), except the following: `enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, notifyOnChangeProps, throwOnError, select, suspense, placeholderData`; which are strictly for useQuery and useInfiniteQuery. You can check the [source code](https://github.com/TanStack/query/blob/7cd2d192e6da3df0b08e334ea1cf04cd70478827/packages/query-core/src/types.ts#L119) for more clarity. diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 489d184aea..ddb13c8de2 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -320,15 +320,10 @@ export class QueryClient { } const query = this.#queryCache.build(this, defaultedOptions) - const isStale = query.isStaleByTime(defaultedOptions.staleTime) - if (isStale && query.state.data && options.prefetchWhenStale) { - query.fetch(defaultedOptions) - return Promise.resolve(query.state.data as TData) - } else if (isStale) { - return query.fetch(defaultedOptions) - } - return query.fetch(defaultedOptions) + return query.isStaleByTime(defaultedOptions.staleTime) + ? query.fetch(defaultedOptions) + : Promise.resolve(query.state.data as TData) } prefetchQuery< diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 4c2987a12e..c4b2b29d4d 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -405,7 +405,6 @@ export interface FetchQueryOptions< * If the data is fresh it will be returned from the cache. */ staleTime?: number - prefetchWhenStale?: boolean } type FetchInfiniteQueryPages = From 1e9899beefd6a76ad4e8670944f595ef66281240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Mon, 4 Mar 2024 09:48:31 +0100 Subject: [PATCH 03/10] fetch(ensureQueryData): Add revalidateIfStale option --- docs/reference/QueryClient.md | 3 ++- packages/query-core/src/queryClient.ts | 14 ++++++++++---- packages/query-core/src/types.ts | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index f2494c2ff7..55f080f797 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -189,7 +189,8 @@ const data = await queryClient.ensureQueryData({ queryKey, queryFn }) **Options** -- The options for `ensureQueryData` are exactly the same as those of [`fetchQuery`](#queryclientfetchquery) except that `queryKey` is required. +- The options for `ensureQueryData` are almost the same as those of [`fetchQuery`](#queryclientfetchquery) except that `queryKey` is required and that is has an extra optional option `revalidateIfStale`. When set `revalidateIfStale` to true the function will make sure the data will refetch in the background while still returning the data from the cache. + **Returns** diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 74f8b23352..28bbd0d804 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -20,6 +20,7 @@ import type { DefaultedQueryObserverOptions, FetchInfiniteQueryOptions, FetchQueryOptions, + EnsureQueryDataOptions, InfiniteData, InvalidateOptions, InvalidateQueryFilters, @@ -129,12 +130,17 @@ export class QueryClient { TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: FetchQueryOptions, + options: EnsureQueryDataOptions, ): Promise { - const cachedData = this.getQueryData(options.queryKey) + const defaultedOptions = this.defaultQueryOptions(options) + const query = this.#queryCache.build(this, defaultedOptions) + + if (query.state.data && query.isStaleByTime(defaultedOptions.staleTime)) { + query.fetch(defaultedOptions) + } - return cachedData !== undefined - ? Promise.resolve(cachedData) + return query.state.data !== undefined + ? Promise.resolve(query.state.data) : this.fetchQuery(options) } diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index c4b2b29d4d..fb0f2433b0 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -407,6 +407,23 @@ export interface FetchQueryOptions< staleTime?: number } +export interface EnsureQueryDataOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, + > extends FetchQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > { + revalidateIfStale?: boolean +} + + type FetchInfiniteQueryPages = | { pages?: never } | { From e442b4f1dacaa39b960cf016b941a15784e6f87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Mon, 4 Mar 2024 11:35:50 +0100 Subject: [PATCH 04/10] fetch(ensureQueryData): Add revalidateIfStale option --- packages/query-core/src/queryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 28bbd0d804..1f419d973c 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -135,7 +135,7 @@ export class QueryClient { const defaultedOptions = this.defaultQueryOptions(options) const query = this.#queryCache.build(this, defaultedOptions) - if (query.state.data && query.isStaleByTime(defaultedOptions.staleTime)) { + if (query.state.data !== undefined && query.isStaleByTime(defaultedOptions.staleTime) && options.revalidateIfStale) { query.fetch(defaultedOptions) } From 412b20f03b70c9496f9f3e6e53ebe92125286d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Mon, 4 Mar 2024 11:42:47 +0100 Subject: [PATCH 05/10] fetch(ensureQueryData): Add revalidateIfStale option --- packages/query-core/src/queryClient.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 3b4313bcad..67e43a79e9 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -132,16 +132,20 @@ export class QueryClient { >( options: EnsureQueryDataOptions, ): Promise { - const defaultedOptions = this.defaultQueryOptions(options) - const query = this.#queryCache.build(this, defaultedOptions) + const cachedData = this.getQueryData(options.queryKey) + + if (cachedData === undefined) return this.fetchQuery(options) + else { + const defaultedOptions = this.defaultQueryOptions(options) + const query = this.#queryCache.build(this, defaultedOptions) + + if (query.isStaleByTime(defaultedOptions.staleTime) && options.revalidateIfStale) { + void this.prefetchQuery(defaultedOptions) + } - if (query.state.data !== undefined && query.isStaleByTime(defaultedOptions.staleTime) && options.revalidateIfStale) { - query.fetch(defaultedOptions) + return Promise.resolve(cachedData) } - return query.state.data !== undefined - ? Promise.resolve(query.state.data) - : this.fetchQuery(options) } getQueriesData( From b81f1705f985e083331f4b13a5e0c9e6b997813c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Mon, 4 Mar 2024 15:10:49 +0100 Subject: [PATCH 06/10] test(ensureQueryClient) implemented test for new option 'revalidateIfStale' --- packages/query-core/src/queryClient.ts | 2 +- .../query-core/src/tests/queryClient.test.tsx | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 67e43a79e9..b498ee53fc 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -18,9 +18,9 @@ import type { DefaultError, DefaultOptions, DefaultedQueryObserverOptions, + EnsureQueryDataOptions, FetchInfiniteQueryOptions, FetchQueryOptions, - EnsureQueryDataOptions, InfiniteData, InvalidateOptions, InvalidateQueryFilters, diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index a975db3971..866a410eb8 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -449,6 +449,24 @@ describe('queryClient', () => { queryClient.ensureQueryData({ queryKey: [key], queryFn }), ).resolves.toEqual('data') }) + + test('should return the cached query data if the query is found and preFetchQuery in the background when revalidateIfStale is set', async () => { + const TIMEOUT = 10 + const key = queryKey() + queryClient.setQueryData([key, 'id'], 'old') + + const queryFn = () => new Promise((resolve) => { + setTimeout(() => resolve('new'), TIMEOUT) + }) + + await expect( + queryClient.ensureQueryData({ queryKey: [key, 'id'], queryFn, revalidateIfStale: true }), + ).resolves.toEqual('old') + await sleep(TIMEOUT + 10) + await expect( + queryClient.ensureQueryData({ queryKey: [key, 'id'], queryFn, revalidateIfStale: true }), + ).resolves.toEqual('new') + }) }) describe('getQueriesData', () => { From 441bd04194f564567a191da4edebf79afac52fc2 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sat, 9 Mar 2024 18:47:42 +0100 Subject: [PATCH 07/10] Update packages/query-core/src/queryClient.ts --- packages/query-core/src/queryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index b9c9a5c456..cb376c9411 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -140,7 +140,7 @@ export class QueryClient { const defaultedOptions = this.defaultQueryOptions(options) const query = this.#queryCache.build(this, defaultedOptions) - if (query.isStaleByTime(defaultedOptions.staleTime) && options.revalidateIfStale) { + if (options.revalidateIfStale && query.isStaleByTime(defaultedOptions.staleTime)) { void this.prefetchQuery(defaultedOptions) } From 588119572505efcc37056e0c1d8421bd15c263dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20de=20Waard?= Date: Tue, 12 Mar 2024 09:32:55 +0100 Subject: [PATCH 08/10] feat(fetchQuery): formatted file --- packages/query-core/src/queryClient.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 992d094084..0f8f601531 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -140,13 +140,15 @@ export class QueryClient { const defaultedOptions = this.defaultQueryOptions(options) const query = this.#queryCache.build(this, defaultedOptions) - if (options.revalidateIfStale && query.isStaleByTime(defaultedOptions.staleTime)) { + if ( + options.revalidateIfStale && + query.isStaleByTime(defaultedOptions.staleTime) + ) { void this.prefetchQuery(defaultedOptions) } return Promise.resolve(cachedData) } - } getQueriesData( From f2ca914173dee8c137959a5f88c7766406b65c9e Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Wed, 13 Mar 2024 08:36:32 +0100 Subject: [PATCH 09/10] Update docs/reference/QueryClient.md --- docs/reference/QueryClient.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index 55f080f797..76b51c3441 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -189,7 +189,11 @@ const data = await queryClient.ensureQueryData({ queryKey, queryFn }) **Options** -- The options for `ensureQueryData` are almost the same as those of [`fetchQuery`](#queryclientfetchquery) except that `queryKey` is required and that is has an extra optional option `revalidateIfStale`. When set `revalidateIfStale` to true the function will make sure the data will refetch in the background while still returning the data from the cache. +- the same options as [`fetchQuery`](#queryclientfetchquery) +- `revalidateIfStale: boolean` + - Optional + - Defaults to `false` + - If set to `true`, stale data will be refetched in the background, but cached data will be returned immediately. **Returns** From 90aa5021ccfd4d470a6cd9345c1ab64435d2e78e Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Wed, 13 Mar 2024 09:23:16 +0100 Subject: [PATCH 10/10] chore: prettier --- docs/reference/QueryClient.md | 1 - .../query-core/src/tests/queryClient.test.tsx | 19 ++++++++++++++----- packages/query-core/src/types.ts | 3 +-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index 76b51c3441..21be683fa7 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -195,7 +195,6 @@ const data = await queryClient.ensureQueryData({ queryKey, queryFn }) - Defaults to `false` - If set to `true`, stale data will be refetched in the background, but cached data will be returned immediately. - **Returns** - `Promise` diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index 167b725b12..ef64f5a3dc 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -456,16 +456,25 @@ describe('queryClient', () => { const key = queryKey() queryClient.setQueryData([key, 'id'], 'old') - const queryFn = () => new Promise((resolve) => { - setTimeout(() => resolve('new'), TIMEOUT) - }) + const queryFn = () => + new Promise((resolve) => { + setTimeout(() => resolve('new'), TIMEOUT) + }) await expect( - queryClient.ensureQueryData({ queryKey: [key, 'id'], queryFn, revalidateIfStale: true }), + queryClient.ensureQueryData({ + queryKey: [key, 'id'], + queryFn, + revalidateIfStale: true, + }), ).resolves.toEqual('old') await sleep(TIMEOUT + 10) await expect( - queryClient.ensureQueryData({ queryKey: [key, 'id'], queryFn, revalidateIfStale: true }), + queryClient.ensureQueryData({ + queryKey: [key, 'id'], + queryFn, + revalidateIfStale: true, + }), ).resolves.toEqual('new') }) }) diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 7c559b77a4..0532a14c10 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -413,7 +413,7 @@ export interface EnsureQueryDataOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = never, - > extends FetchQueryOptions< +> extends FetchQueryOptions< TQueryFnData, TError, TData, @@ -423,7 +423,6 @@ export interface EnsureQueryDataOptions< revalidateIfStale?: boolean } - type FetchInfiniteQueryPages = | { pages?: never } | {