From 2dc373b77a9606876c09a28dcf8e966f6678f802 Mon Sep 17 00:00:00 2001 From: John Pettersson <33894408+lotusjohn@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:49:07 +0200 Subject: [PATCH 1/8] Add possibility to pass a callback to enabled. --- .../src/__tests__/queryObserver.test.tsx | 59 +++++++++++++++++++ packages/query-core/src/query.ts | 6 +- packages/query-core/src/queryClient.ts | 4 +- packages/query-core/src/queryObserver.ts | 44 +++++++++++--- packages/query-core/src/types.ts | 3 +- 5 files changed, 104 insertions(+), 12 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test.tsx b/packages/query-core/src/__tests__/queryObserver.test.tsx index bc6d4e8d13..8555ed6cd3 100644 --- a/packages/query-core/src/__tests__/queryObserver.test.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test.tsx @@ -52,6 +52,51 @@ describe('queryObserver', () => { unsubscribe() }) + test('Should not refetch when enabled is a callback and returns false', async () => { + const key = queryKey() + let count = 0 + let enabled = true + const observer = new QueryObserver(queryClient, { + queryKey: key, + staleTime: Infinity, + enabled: () => enabled, + queryFn: async () => { + await sleep(10) + count++ + return 'data' + }, + }) + + let unsubscribe = observer.subscribe(vi.fn()) + + // unsubscribe before data comes in + unsubscribe() + expect(count).toBe(0) + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'fetching', + data: undefined, + }) + + await waitFor(() => expect(count).toBe(1)) + + // re-subscribe after data comes in + unsubscribe = observer.subscribe(vi.fn()) + + expect(observer.getCurrentResult()).toMatchObject({ + status: 'success', + data: 'data', + }) + + enabled = false + + queryClient.invalidateQueries({ queryKey: key, refetchType: 'all' }) + + expect(count).toBe(1) + + unsubscribe() + }) + test('should be able to read latest data when re-subscribing (but not re-fetching)', async () => { const key = queryKey() let count = 0 @@ -429,6 +474,20 @@ describe('queryObserver', () => { expect(queryFn).toHaveBeenCalledTimes(0) }) + test('should not trigger a fetch when subscribed and disabled by callback', async () => { + const key = queryKey() + const queryFn = vi.fn, string>().mockReturnValue('data') + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn, + enabled: () => false, + }) + const unsubscribe = observer.subscribe(() => undefined) + await sleep(1) + unsubscribe() + expect(queryFn).toHaveBeenCalledTimes(0) + }) + test('should not trigger a fetch when not subscribed', async () => { const key = queryKey() const queryFn = vi.fn, string>().mockReturnValue('data') diff --git a/packages/query-core/src/query.ts b/packages/query-core/src/query.ts index 2ccb91e201..ffc9c91fa1 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -244,7 +244,11 @@ export class Query< } isActive(): boolean { - return this.observers.some((observer) => observer.options.enabled !== false) + return this.observers.some((observer) => + typeof observer.options.enabled === 'function' + ? observer.options.enabled() !== false + : observer.options.enabled !== false, + ) } isDisabled(): boolean { diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 1bc89fccba..e42d620fdc 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -555,7 +555,9 @@ export class QueryClient { } if ( - defaultedOptions.enabled !== true && + (typeof defaultedOptions.enabled === 'function' + ? defaultedOptions.enabled() !== true + : defaultedOptions.enabled !== true) && defaultedOptions.queryFn === skipToken ) { defaultedOptions.enabled = false diff --git a/packages/query-core/src/queryObserver.ts b/packages/query-core/src/queryObserver.ts index ef25d32e8d..0d59af24ed 100644 --- a/packages/query-core/src/queryObserver.ts +++ b/packages/query-core/src/queryObserver.ts @@ -149,9 +149,12 @@ export class QueryObserver< if ( this.options.enabled !== undefined && - typeof this.options.enabled !== 'boolean' + typeof this.options.enabled !== 'boolean' && + typeof this.options.enabled !== 'function' ) { - throw new Error('Expected enabled to be a boolean') + throw new Error( + 'Expected enabled to be a boolean or a callback that returns a boolean', + ) } this.#updateQuery() @@ -190,7 +193,12 @@ export class QueryObserver< if ( mounted && (this.#currentQuery !== prevQuery || - this.options.enabled !== prevOptions.enabled || + (typeof this.options.enabled === 'function' + ? this.options.enabled() + : this.options.enabled) !== + (typeof prevOptions.enabled === 'function' + ? prevOptions.enabled() + : prevOptions.enabled) || resolveStaleTime(this.options.staleTime, this.#currentQuery) !== resolveStaleTime(prevOptions.staleTime, this.#currentQuery)) ) { @@ -203,7 +211,12 @@ export class QueryObserver< if ( mounted && (this.#currentQuery !== prevQuery || - this.options.enabled !== prevOptions.enabled || + (typeof this.options.enabled === 'function' + ? this.options.enabled() + : this.options.enabled) !== + (typeof prevOptions.enabled === 'function' + ? prevOptions.enabled() + : prevOptions.enabled) || nextRefetchInterval !== this.#currentRefetchInterval) ) { this.#updateRefetchInterval(nextRefetchInterval) @@ -377,7 +390,9 @@ export class QueryObserver< if ( isServer || - this.options.enabled === false || + (typeof this.options.enabled === 'function' + ? this.options.enabled() + : this.options.enabled) === false || !isValidTimeout(this.#currentRefetchInterval) || this.#currentRefetchInterval === 0 ) { @@ -692,7 +707,9 @@ function shouldLoadOnMount( options: QueryObserverOptions, ): boolean { return ( - options.enabled !== false && + (typeof options.enabled === 'function' + ? options.enabled() + : options.enabled) !== false && query.state.data === undefined && !(query.state.status === 'error' && options.retryOnMount === false) ) @@ -716,7 +733,11 @@ function shouldFetchOn( (typeof options)['refetchOnWindowFocus'] & (typeof options)['refetchOnReconnect'], ) { - if (options.enabled !== false) { + if ( + (typeof options.enabled === 'function' + ? options.enabled() + : options.enabled) !== false + ) { const value = typeof field === 'function' ? field(query) : field return value === 'always' || (value !== false && isStale(query, options)) @@ -731,7 +752,10 @@ function shouldFetchOptionally( prevOptions: QueryObserverOptions, ): boolean { return ( - (query !== prevQuery || prevOptions.enabled === false) && + (query !== prevQuery || + (typeof prevOptions.enabled === 'function' + ? prevOptions.enabled() + : prevOptions.enabled) === false) && (!options.suspense || query.state.status !== 'error') && isStale(query, options) ) @@ -742,7 +766,9 @@ function isStale( options: QueryObserverOptions, ): boolean { return ( - options.enabled !== false && + (typeof options.enabled === 'function' + ? options.enabled() + : options.enabled) !== false && query.isStaleByTime(resolveStaleTime(options.staleTime, query)) ) } diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 7b812a061f..b5e350c171 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -255,9 +255,10 @@ export interface QueryObserverOptions< /** * Set this to `false` to disable automatic refetching when the query mounts or changes query keys. * To refetch the query, use the `refetch` method returned from the `useQuery` instance. + * You can also pass a callback that returns a boolean to check this condition dynamically. * Defaults to `true`. */ - enabled?: boolean + enabled?: (() => boolean) | boolean /** * The time in milliseconds after data is considered stale. * If set to `Infinity`, the data will never be considered stale. From 3101d1a83242f3069f456a72e0d7f5ef3ce536b4 Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Mon, 24 Jun 2024 13:50:52 +0200 Subject: [PATCH 2/8] Refactor into using the same pattern as resolving staletime, with the use of a resolveEnabled util function --- packages/query-core/src/query.ts | 14 +++++--- packages/query-core/src/queryClient.ts | 4 +-- packages/query-core/src/queryObserver.ts | 43 +++++++----------------- packages/query-core/src/types.ts | 11 +++++- packages/query-core/src/utils.ts | 13 +++++++ 5 files changed, 46 insertions(+), 39 deletions(-) diff --git a/packages/query-core/src/query.ts b/packages/query-core/src/query.ts index ffc9c91fa1..fa2900d098 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -1,4 +1,10 @@ -import { ensureQueryFn, noop, replaceData, timeUntilStale } from './utils' +import { + ensureQueryFn, + noop, + replaceData, + resolveEnabled, + timeUntilStale, +} from './utils' import { notifyManager } from './notifyManager' import { canFetch, createRetryer, isCancelledError } from './retryer' import { Removable } from './removable' @@ -244,10 +250,8 @@ export class Query< } isActive(): boolean { - return this.observers.some((observer) => - typeof observer.options.enabled === 'function' - ? observer.options.enabled() !== false - : observer.options.enabled !== false, + return this.observers.some( + (observer) => resolveEnabled(observer.options.enabled, this) !== false, ) } diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index e42d620fdc..1bc89fccba 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -555,9 +555,7 @@ export class QueryClient { } if ( - (typeof defaultedOptions.enabled === 'function' - ? defaultedOptions.enabled() !== true - : defaultedOptions.enabled !== true) && + defaultedOptions.enabled !== true && defaultedOptions.queryFn === skipToken ) { defaultedOptions.enabled = false diff --git a/packages/query-core/src/queryObserver.ts b/packages/query-core/src/queryObserver.ts index 0d59af24ed..0a73184241 100644 --- a/packages/query-core/src/queryObserver.ts +++ b/packages/query-core/src/queryObserver.ts @@ -3,6 +3,7 @@ import { isValidTimeout, noop, replaceData, + resolveEnabled, resolveStaleTime, shallowEqualObjects, timeUntilStale, @@ -150,7 +151,9 @@ export class QueryObserver< if ( this.options.enabled !== undefined && typeof this.options.enabled !== 'boolean' && - typeof this.options.enabled !== 'function' + typeof this.options.enabled !== 'function' && + typeof resolveEnabled(this.options.enabled, this.#currentQuery) !== + 'boolean' ) { throw new Error( 'Expected enabled to be a boolean or a callback that returns a boolean', @@ -193,12 +196,8 @@ export class QueryObserver< if ( mounted && (this.#currentQuery !== prevQuery || - (typeof this.options.enabled === 'function' - ? this.options.enabled() - : this.options.enabled) !== - (typeof prevOptions.enabled === 'function' - ? prevOptions.enabled() - : prevOptions.enabled) || + resolveEnabled(this.options.enabled, this.#currentQuery) !== + resolveEnabled(prevOptions.enabled, this.#currentQuery) || resolveStaleTime(this.options.staleTime, this.#currentQuery) !== resolveStaleTime(prevOptions.staleTime, this.#currentQuery)) ) { @@ -211,12 +210,8 @@ export class QueryObserver< if ( mounted && (this.#currentQuery !== prevQuery || - (typeof this.options.enabled === 'function' - ? this.options.enabled() - : this.options.enabled) !== - (typeof prevOptions.enabled === 'function' - ? prevOptions.enabled() - : prevOptions.enabled) || + resolveEnabled(this.options.enabled, this.#currentQuery) !== + resolveEnabled(prevOptions.enabled, this.#currentQuery) || nextRefetchInterval !== this.#currentRefetchInterval) ) { this.#updateRefetchInterval(nextRefetchInterval) @@ -390,9 +385,7 @@ export class QueryObserver< if ( isServer || - (typeof this.options.enabled === 'function' - ? this.options.enabled() - : this.options.enabled) === false || + resolveEnabled(this.options.enabled, this.#currentQuery) === false || !isValidTimeout(this.#currentRefetchInterval) || this.#currentRefetchInterval === 0 ) { @@ -707,9 +700,7 @@ function shouldLoadOnMount( options: QueryObserverOptions, ): boolean { return ( - (typeof options.enabled === 'function' - ? options.enabled() - : options.enabled) !== false && + resolveEnabled(options.enabled, query) !== false && query.state.data === undefined && !(query.state.status === 'error' && options.retryOnMount === false) ) @@ -733,11 +724,7 @@ function shouldFetchOn( (typeof options)['refetchOnWindowFocus'] & (typeof options)['refetchOnReconnect'], ) { - if ( - (typeof options.enabled === 'function' - ? options.enabled() - : options.enabled) !== false - ) { + if (resolveEnabled(options.enabled, query) !== false) { const value = typeof field === 'function' ? field(query) : field return value === 'always' || (value !== false && isStale(query, options)) @@ -753,9 +740,7 @@ function shouldFetchOptionally( ): boolean { return ( (query !== prevQuery || - (typeof prevOptions.enabled === 'function' - ? prevOptions.enabled() - : prevOptions.enabled) === false) && + resolveEnabled(prevOptions.enabled, query) === false) && (!options.suspense || query.state.status !== 'error') && isStale(query, options) ) @@ -766,9 +751,7 @@ function isStale( options: QueryObserverOptions, ): boolean { return ( - (typeof options.enabled === 'function' - ? options.enabled() - : options.enabled) !== false && + resolveEnabled(options.enabled, query) !== false && query.isStaleByTime(resolveStaleTime(options.staleTime, query)) ) } diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index b5e350c171..514b1c69fb 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -54,6 +54,15 @@ export type StaleTime< TQueryKey extends QueryKey = QueryKey, > = number | ((query: Query) => number) +export type Enabled< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = + | boolean + | ((query: Query) => boolean) + export type QueryPersister< T = unknown, TQueryKey extends QueryKey = QueryKey, @@ -258,7 +267,7 @@ export interface QueryObserverOptions< * You can also pass a callback that returns a boolean to check this condition dynamically. * Defaults to `true`. */ - enabled?: (() => boolean) | boolean + enabled?: Enabled /** * The time in milliseconds after data is considered stale. * If set to `Infinity`, the data will never be considered stale. diff --git a/packages/query-core/src/utils.ts b/packages/query-core/src/utils.ts index 3cb9374e58..8b498ae1e4 100644 --- a/packages/query-core/src/utils.ts +++ b/packages/query-core/src/utils.ts @@ -1,5 +1,6 @@ import type { DefaultError, + Enabled, FetchStatus, MutationKey, MutationStatus, @@ -100,6 +101,18 @@ export function resolveStaleTime< return typeof staleTime === 'function' ? staleTime(query) : staleTime } +export function resolveEnabled< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + enabled: undefined | Enabled, + query: Query, +): boolean | undefined { + return typeof enabled === 'function' ? enabled(query) : enabled +} + export function matchQuery( filters: QueryFilters, query: Query, From 108101e156b2ac0f642231bea269abd143ba343c Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Mon, 24 Jun 2024 16:13:15 +0200 Subject: [PATCH 3/8] Update tests for enabled option as a callback --- .../src/__tests__/queryObserver.test.tsx | 198 +++++++++++++++--- 1 file changed, 167 insertions(+), 31 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test.tsx b/packages/query-core/src/__tests__/queryObserver.test.tsx index 8555ed6cd3..a8a55c1548 100644 --- a/packages/query-core/src/__tests__/queryObserver.test.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test.tsx @@ -52,49 +52,171 @@ describe('queryObserver', () => { unsubscribe() }) - test('Should not refetch when enabled is a callback and returns false', async () => { - const key = queryKey() - let count = 0 - let enabled = true - const observer = new QueryObserver(queryClient, { - queryKey: key, - staleTime: Infinity, - enabled: () => enabled, - queryFn: async () => { - await sleep(10) - count++ - return 'data' - }, + describe('enabled is a callback that initially returns false and', () => { + let observer: QueryObserver + let enabled: boolean + let count: number + let key: string[] + + beforeEach(() => { + key = queryKey() + count = 0 + enabled = false + + observer = new QueryObserver(queryClient, { + queryKey: key, + staleTime: Infinity, + enabled: () => enabled, + queryFn: async () => { + await sleep(10) + count++ + return 'data' + }, + }) }) - let unsubscribe = observer.subscribe(vi.fn()) + test('should not fetch on mount', () => { + let unsubscribe = observer.subscribe(vi.fn()) - // unsubscribe before data comes in - unsubscribe() - expect(count).toBe(0) - expect(observer.getCurrentResult()).toMatchObject({ - status: 'pending', - fetchStatus: 'fetching', - data: undefined, + // Has not fetched and is not fetching since its disabled + expect(count).toBe(0) + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'idle', + data: undefined, + }) + + unsubscribe() }) - await waitFor(() => expect(count).toBe(1)) + test('should not be refetched when invalidated with refetchType: all', async () => { + let unsubscribe = observer.subscribe(vi.fn()) - // re-subscribe after data comes in - unsubscribe = observer.subscribe(vi.fn()) + queryClient.invalidateQueries({ queryKey: key, refetchType: 'all' }) - expect(observer.getCurrentResult()).toMatchObject({ - status: 'success', - data: 'data', + //So we still expect it to not have fetched and not be fetching + expect(count).toBe(0) + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'idle', + data: undefined, + }) + await waitFor(() => expect(count).toBe(0)) + + unsubscribe() }) - enabled = false + test('should still trigger a fetch when refetch is called', async () => { + let unsubscribe = observer.subscribe(vi.fn()) - queryClient.invalidateQueries({ queryKey: key, refetchType: 'all' }) + expect(enabled).toBe(false) - expect(count).toBe(1) + //Not the same with explicit refetch, this will override enabled and trigger a fetch anyway + observer.refetch() - unsubscribe() + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'fetching', + data: undefined, + }) + + await waitFor(() => expect(count).toBe(1)) + expect(observer.getCurrentResult()).toMatchObject({ + status: 'success', + fetchStatus: 'idle', + data: 'data', + }) + + unsubscribe() + }) + + test('should fetch if unsubcribed, then enabled returns true, and then re-suscribed', async () => { + let unsubscribe = observer.subscribe(vi.fn()) + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'idle', + data: undefined, + }) + + unsubscribe() + + enabled = true + + unsubscribe = observer.subscribe(vi.fn()) + + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'fetching', + data: undefined, + }) + + await waitFor(() => expect(count).toBe(1)) + + unsubscribe() + }) + + test('should not be refetched if not subscribed to after enabled was toggled to true', async () => { + let unsubscribe = observer.subscribe(vi.fn()) + + // Toggle enabled + enabled = true + + unsubscribe() + + queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' }) + + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'idle', + data: undefined, + }) + expect(count).toBe(0) + }) + + test('should not be refetched if not subscribed to after enabled was toggled to true', async () => { + let unsubscribe = observer.subscribe(vi.fn()) + + // Toggle enabled + enabled = true + + queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' }) + + expect(observer.getCurrentResult()).toMatchObject({ + status: 'pending', + fetchStatus: 'fetching', + data: undefined, + }) + await waitFor(() => expect(count).toBe(1)) + + unsubscribe() + }) + + test('should handle that the enabled callback updates the return value', async () => { + let unsubscribe = observer.subscribe(vi.fn()) + + // Toggle enabled + enabled = true + + queryClient.invalidateQueries({ queryKey: key, refetchType: 'inactive' }) + + //should not refetch since it was active and we only refetch inactive + await waitFor(() => expect(count).toBe(0)) + + queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' }) + + //should refetch since it was active and we refetch active + await waitFor(() => expect(count).toBe(1)) + + // Toggle enabled + enabled = false + + //should not refetch since it is not active and we only refetch active + queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' }) + + await waitFor(() => expect(count).toBe(1)) + + unsubscribe() + }) }) test('should be able to read latest data when re-subscribing (but not re-fetching)', async () => { @@ -488,6 +610,20 @@ describe('queryObserver', () => { expect(queryFn).toHaveBeenCalledTimes(0) }) + test('should not trigger a fetch when subscribed and disabled by callback', async () => { + const key = queryKey() + const queryFn = vi.fn, string>().mockReturnValue('data') + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn, + enabled: () => false, + }) + const unsubscribe = observer.subscribe(() => undefined) + await sleep(1) + unsubscribe() + expect(queryFn).toHaveBeenCalledTimes(0) + }) + test('should not trigger a fetch when not subscribed', async () => { const key = queryKey() const queryFn = vi.fn, string>().mockReturnValue('data') From ba701289f0887e6a803c98da6230c5d6066b5341 Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Mon, 24 Jun 2024 16:33:54 +0200 Subject: [PATCH 4/8] update docs --- .../react/guides/disabling-queries.md | 2 +- docs/framework/react/react-native.md | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/framework/react/guides/disabling-queries.md b/docs/framework/react/guides/disabling-queries.md index 6893039809..da173f688b 100644 --- a/docs/framework/react/guides/disabling-queries.md +++ b/docs/framework/react/guides/disabling-queries.md @@ -3,7 +3,7 @@ id: disabling-queries title: Disabling/Pausing Queries --- -If you ever want to disable a query from automatically running, you can use the `enabled = false` option. +If you ever want to disable a query from automatically running, you can use the `enabled = false` option. The enabled option also accepts a callback that returns a boolean. When `enabled` is `false`: diff --git a/docs/framework/react/react-native.md b/docs/framework/react/react-native.md index c14f4c30fd..ad8d230c31 100644 --- a/docs/framework/react/react-native.md +++ b/docs/framework/react/react-native.md @@ -143,3 +143,34 @@ function MyComponent() { return DataUpdatedAt: {dataUpdatedAt} } ``` + +## Disable queries on out of focus screens + +Enabled can also be set to a callback to support disabling queries on out of focus screens without state and re-rendering on navigation, similar to how notifyOnChangeProps works but in addition it wont trigger refetching when invalidating queries with refetchType active. + +```tsx +import React from 'react' +import { useFocusEffect } from '@react-navigation/native' + +export function useQueryFocusAware(notifyOnChangeProps?: NotifyOnChangeProps) { + const focusedRef = React.useRef(true) + + useFocusEffect( + React.useCallback(() => { + focusedRef.current = true + + return () => { + focusedRef.current = false + } + }, []), + ) + + return () => focusRef.current + + useQuery({ + queryKey: ['key'], + queryFn: () => fetch(...), + enabled: () => focusedRef.current, + }) +} +``` From 4ec03e89a942df90b8963ea7cd4e31d631c2b00d Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Mon, 24 Jun 2024 16:43:23 +0200 Subject: [PATCH 5/8] remove typo --- packages/query-core/src/__tests__/queryObserver.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test.tsx b/packages/query-core/src/__tests__/queryObserver.test.tsx index a8a55c1548..7f13c6d3bf 100644 --- a/packages/query-core/src/__tests__/queryObserver.test.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test.tsx @@ -52,7 +52,7 @@ describe('queryObserver', () => { unsubscribe() }) - describe('enabled is a callback that initially returns false and', () => { + describe('enabled is a callback that initially returns false', () => { let observer: QueryObserver let enabled: boolean let count: number From 4fcc32fe9160c64584734bb15abcb3afd7601a86 Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Mon, 24 Jun 2024 16:52:07 +0200 Subject: [PATCH 6/8] Update enabled type in docs --- docs/framework/react/reference/useQuery.md | 2 +- packages/query-core/src/types.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/framework/react/reference/useQuery.md b/docs/framework/react/reference/useQuery.md index 02137a3cdc..c71ba342ae 100644 --- a/docs/framework/react/reference/useQuery.md +++ b/docs/framework/react/reference/useQuery.md @@ -70,7 +70,7 @@ const { - The function that the query will use to request data. - Receives a [QueryFunctionContext](../../guides/query-functions#queryfunctioncontext) - Must return a promise that will either resolve data or throw an error. The data cannot be `undefined`. -- `enabled: boolean` +- `enabled: boolean | (query: Query) => boolean` - Set this to `false` to disable this query from automatically running. - Can be used for [Dependent Queries](../../guides/dependent-queries). - `networkMode: 'online' | 'always' | 'offlineFirst` diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 514b1c69fb..75a130fd7e 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -262,9 +262,9 @@ export interface QueryObserverOptions< 'queryKey' > { /** - * Set this to `false` to disable automatic refetching when the query mounts or changes query keys. + * Set this to `false` or a function that returns `false` to disable automatic refetching when the query mounts or changes query keys. * To refetch the query, use the `refetch` method returned from the `useQuery` instance. - * You can also pass a callback that returns a boolean to check this condition dynamically. + * Accepts a boolean or function that returns a boolean. * Defaults to `true`. */ enabled?: Enabled From 7ed736a5e0a6b79d0129e43a992dc9be3c06c359 Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Tue, 25 Jun 2024 08:00:00 +0200 Subject: [PATCH 7/8] remove duplicated test case --- .../src/__tests__/queryObserver.test.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test.tsx b/packages/query-core/src/__tests__/queryObserver.test.tsx index 7f13c6d3bf..3cef2d3b82 100644 --- a/packages/query-core/src/__tests__/queryObserver.test.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test.tsx @@ -610,20 +610,6 @@ describe('queryObserver', () => { expect(queryFn).toHaveBeenCalledTimes(0) }) - test('should not trigger a fetch when subscribed and disabled by callback', async () => { - const key = queryKey() - const queryFn = vi.fn, string>().mockReturnValue('data') - const observer = new QueryObserver(queryClient, { - queryKey: key, - queryFn, - enabled: () => false, - }) - const unsubscribe = observer.subscribe(() => undefined) - await sleep(1) - unsubscribe() - expect(queryFn).toHaveBeenCalledTimes(0) - }) - test('should not trigger a fetch when not subscribed', async () => { const key = queryKey() const queryFn = vi.fn, string>().mockReturnValue('data') From 8c9b5a60a16e4209a64bb9bc3217e570ac60d0f8 Mon Sep 17 00:00:00 2001 From: John Pettersson Date: Tue, 25 Jun 2024 14:07:57 +0200 Subject: [PATCH 8/8] Fix eslint errors --- .../src/__tests__/queryObserver.test.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test.tsx b/packages/query-core/src/__tests__/queryObserver.test.tsx index 3cef2d3b82..c3a1ebb037 100644 --- a/packages/query-core/src/__tests__/queryObserver.test.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test.tsx @@ -53,10 +53,10 @@ describe('queryObserver', () => { }) describe('enabled is a callback that initially returns false', () => { - let observer: QueryObserver + let observer: QueryObserver> let enabled: boolean let count: number - let key: string[] + let key: Array beforeEach(() => { key = queryKey() @@ -76,7 +76,7 @@ describe('queryObserver', () => { }) test('should not fetch on mount', () => { - let unsubscribe = observer.subscribe(vi.fn()) + const unsubscribe = observer.subscribe(vi.fn()) // Has not fetched and is not fetching since its disabled expect(count).toBe(0) @@ -89,8 +89,8 @@ describe('queryObserver', () => { unsubscribe() }) - test('should not be refetched when invalidated with refetchType: all', async () => { - let unsubscribe = observer.subscribe(vi.fn()) + test('should not be re-fetched when invalidated with refetchType: all', async () => { + const unsubscribe = observer.subscribe(vi.fn()) queryClient.invalidateQueries({ queryKey: key, refetchType: 'all' }) @@ -107,7 +107,7 @@ describe('queryObserver', () => { }) test('should still trigger a fetch when refetch is called', async () => { - let unsubscribe = observer.subscribe(vi.fn()) + const unsubscribe = observer.subscribe(vi.fn()) expect(enabled).toBe(false) @@ -130,7 +130,7 @@ describe('queryObserver', () => { unsubscribe() }) - test('should fetch if unsubcribed, then enabled returns true, and then re-suscribed', async () => { + test('should fetch if unsubscribed, then enabled returns true, and then re-subscribed', async () => { let unsubscribe = observer.subscribe(vi.fn()) expect(observer.getCurrentResult()).toMatchObject({ status: 'pending', @@ -155,8 +155,8 @@ describe('queryObserver', () => { unsubscribe() }) - test('should not be refetched if not subscribed to after enabled was toggled to true', async () => { - let unsubscribe = observer.subscribe(vi.fn()) + test('should not be re-fetched if not subscribed to after enabled was toggled to true', async () => { + const unsubscribe = observer.subscribe(vi.fn()) // Toggle enabled enabled = true @@ -173,8 +173,8 @@ describe('queryObserver', () => { expect(count).toBe(0) }) - test('should not be refetched if not subscribed to after enabled was toggled to true', async () => { - let unsubscribe = observer.subscribe(vi.fn()) + test('should not be re-fetched if not subscribed to after enabled was toggled to true', async () => { + const unsubscribe = observer.subscribe(vi.fn()) // Toggle enabled enabled = true @@ -192,7 +192,7 @@ describe('queryObserver', () => { }) test('should handle that the enabled callback updates the return value', async () => { - let unsubscribe = observer.subscribe(vi.fn()) + const unsubscribe = observer.subscribe(vi.fn()) // Toggle enabled enabled = true