From 13473d6f09d75696790d81b6cfb5bc6d6dde8fba Mon Sep 17 00:00:00 2001 From: kisk Date: Tue, 27 Jan 2026 12:00:30 +0200 Subject: [PATCH] feat(query-db-collection): refetch and network options to QueryObserver --- .changeset/warm-teeth-train.md | 5 + packages/db/src/query/builder/index.ts | 2 +- packages/query-db-collection/src/query.ts | 28 +- .../query-db-collection/tests/query.test.ts | 317 ++++++++++++++++++ 4 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 .changeset/warm-teeth-train.md diff --git a/.changeset/warm-teeth-train.md b/.changeset/warm-teeth-train.md new file mode 100644 index 000000000..12bfe6d98 --- /dev/null +++ b/.changeset/warm-teeth-train.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': minor +--- + +Add refetchOnWindowFocus and refetchOnReconnect and networkMode configuration options to Query Collections for better control over query refetch behavior diff --git a/packages/db/src/query/builder/index.ts b/packages/db/src/query/builder/index.ts index 6ef4d1ddc..fefc23d34 100644 --- a/packages/db/src/query/builder/index.ts +++ b/packages/db/src/query/builder/index.ts @@ -34,8 +34,8 @@ import type { import type { CompareOptions, Context, - GetResult, FunctionalHavingRow, + GetResult, GroupByCallback, JoinOnCallback, MergeContextForJoinCallback, diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 6b3f83a90..2a83757d7 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -112,6 +112,27 @@ export interface QueryCollectionConfig< Array, TQueryKey >[`staleTime`] + refetchOnWindowFocus?: QueryObserverOptions< + Array, + TError, + Array, + Array, + TQueryKey + >['refetchOnWindowFocus'] + refetchOnReconnect?: QueryObserverOptions< + Array, + TError, + Array, + Array, + TQueryKey + >['refetchOnReconnect'] + networkMode?: QueryObserverOptions< + Array, + TError, + Array, + Array, + TQueryKey + >['networkMode'] /** * Metadata to pass to the query. @@ -538,6 +559,9 @@ export function queryCollectionOptions( retry, retryDelay, staleTime, + refetchOnWindowFocus, + refetchOnReconnect, + networkMode, getKey, onInsert, onUpdate, @@ -717,7 +741,6 @@ export function queryCollectionOptions( queryKey: key, queryFn: queryFunction, meta: extendedMeta, - structuralSharing: true, notifyOnChangeProps: `all`, // Only include options that are explicitly defined to allow QueryClient defaultOptions to be used @@ -726,6 +749,9 @@ export function queryCollectionOptions( ...(retry !== undefined && { retry }), ...(retryDelay !== undefined && { retryDelay }), ...(staleTime !== undefined && { staleTime }), + ...(refetchOnWindowFocus !== undefined && { refetchOnWindowFocus }), + ...(refetchOnReconnect !== undefined && { refetchOnReconnect }), + ...(networkMode !== undefined && { networkMode }), } const localObserver = new QueryObserver< diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 0a623de61..10871a108 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -5187,4 +5187,321 @@ describe(`QueryCollection`, () => { customQueryClient.clear() }) }) + + describe(`refetchOnWindowFocus`, () => { + it(`should accept refetchOnWindowFocus as boolean true`, async () => { + const queryKey = [`refetchOnFocus`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-focus`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnWindowFocus: true, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should accept refetchOnWindowFocus as boolean false`, async () => { + const queryKey = [`refetchOnFocusDisabled`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-focus-disabled`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnWindowFocus: false, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should accept refetchOnWindowFocus as function predicate`, async () => { + const queryKey = [`refetchOnFocusPredicate`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + const shouldRefetch = vi.fn().mockReturnValue(true) + + const config: QueryCollectionConfig = { + id: `test-focus-predicate`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnWindowFocus: shouldRefetch as any, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + }) + + describe(`refetchOnReconnect`, () => { + it(`should accept refetchOnReconnect as boolean true`, async () => { + const queryKey = [`refetchOnReconnect`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-reconnect`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnReconnect: true, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should accept refetchOnReconnect as boolean false`, async () => { + const queryKey = [`refetchOnReconnectDisabled`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-reconnect-disabled`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnReconnect: false, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should accept refetchOnReconnect as function predicate`, async () => { + const queryKey = [`refetchOnReconnectPredicate`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + const shouldRefetch = vi.fn().mockReturnValue(true) + + const config: QueryCollectionConfig = { + id: `test-reconnect-predicate`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnReconnect: shouldRefetch as any, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + }) + + describe(`networkMode`, () => { + it(`should respect networkMode 'always'`, async () => { + const queryKey = [`networkModeAlways`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-network-always`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + networkMode: `always`, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should respect networkMode 'offlineFirst'`, async () => { + const queryKey = [`networkModeOfflineFirst`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-network-offline-first`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + networkMode: `offlineFirst`, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should respect networkMode 'online'`, async () => { + const queryKey = [`networkModeOnline`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-network-online`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + networkMode: `online`, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + }) + + describe(`combined refetch options`, () => { + it(`should work with both refetchOnWindowFocus and refetchOnReconnect enabled`, async () => { + const queryKey = [`combinedRefetch`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-combined`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + + it(`should work with networkMode and refetch triggers`, async () => { + const queryKey = [`combinedNetworkAndRefetch`] + const items: Array = [{ id: `1`, name: `Item 1` }] + + const queryFn = vi.fn().mockResolvedValue(items) + + const config: QueryCollectionConfig = { + id: `test-combined-network`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + refetchOnWindowFocus: true, + networkMode: `offlineFirst`, + staleTime: 0, + } + + const options = queryCollectionOptions(config) + const collection = createCollection(options) + + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.size).toBe(1) + }) + + expect(collection.get(`1`)).toEqual(items[0]) + }) + }) })