Skip to content

Commit fab76e6

Browse files
authored
feat(query): functions for refetchOn options (#3518)
* feat(query): functions for refetchOn options make refetchOn mount/reconnect/windowFocus also accept a function that gets evaluated on the fly with the current data and query * feat(query): functions for refetchOn options docs * feat(query): functions for refetchOn options tests * feat(query): functions for refetchOn options only pass query to the functions as they don't lean towards error or data * feat(query): functions for refetchOn options fix docs * feat(query): functions for refetchOn options fix docs
1 parent 4bd66a3 commit fab76e6

File tree

4 files changed

+132
-39
lines changed

4 files changed

+132
-39
lines changed

docs/src/pages/reference/useQuery.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,24 +110,27 @@ const result = useQuery({
110110
- `refetchIntervalInBackground: boolean`
111111
- Optional
112112
- If set to `true`, queries that are set to continuously refetch with a `refetchInterval` will continue to refetch while their tab/window is in the background
113-
- `refetchOnMount: boolean | "always"`
113+
- `refetchOnMount: boolean | "always" | ((query: Query) => boolean | "always")`
114114
- Optional
115115
- Defaults to `true`
116116
- If set to `true`, the query will refetch on mount if the data is stale.
117117
- If set to `false`, the query will not refetch on mount.
118118
- If set to `"always"`, the query will always refetch on mount.
119-
- `refetchOnWindowFocus: boolean | "always"`
119+
- If set to a function, the function will be executed with the query to compute the value
120+
- `refetchOnWindowFocus: boolean | "always" | ((query: Query) => boolean | "always")`
120121
- Optional
121122
- Defaults to `true`
122123
- If set to `true`, the query will refetch on window focus if the data is stale.
123124
- If set to `false`, the query will not refetch on window focus.
124125
- If set to `"always"`, the query will always refetch on window focus.
125-
- `refetchOnReconnect: boolean | "always"`
126+
- If set to a function, the function will be executed with the query to compute the value
127+
- `refetchOnReconnect: boolean | "always" | ((query: Query) => boolean | "always")`
126128
- Optional
127129
- Defaults to `true`
128130
- If set to `true`, the query will refetch on reconnect if the data is stale.
129131
- If set to `false`, the query will not refetch on reconnect.
130132
- If set to `"always"`, the query will always refetch on reconnect.
133+
- If set to a function, the function will be executed with the query to compute the value
131134
- `notifyOnChangeProps: string[] | "tracked"`
132135
- Optional
133136
- If set, the component will only re-render if any of the listed properties change.

src/core/queryObserver.ts

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,19 @@ export class QueryObserver<
122122
}
123123

124124
shouldFetchOnReconnect(): boolean {
125-
return shouldFetchOnReconnect(this.currentQuery, this.options)
125+
return shouldFetchOn(
126+
this.currentQuery,
127+
this.options,
128+
this.options.refetchOnReconnect
129+
)
126130
}
127131

128132
shouldFetchOnWindowFocus(): boolean {
129-
return shouldFetchOnWindowFocus(this.currentQuery, this.options)
133+
return shouldFetchOn(
134+
this.currentQuery,
135+
this.options,
136+
this.options.refetchOnWindowFocus
137+
)
130138
}
131139

132140
destroy(): void {
@@ -746,47 +754,30 @@ function shouldLoadOnMount(
746754
)
747755
}
748756

749-
function shouldRefetchOnMount(
750-
query: Query<any, any, any, any>,
751-
options: QueryObserverOptions<any, any, any, any>
752-
): boolean {
753-
return (
754-
options.enabled !== false &&
755-
query.state.dataUpdatedAt > 0 &&
756-
(options.refetchOnMount === 'always' ||
757-
(options.refetchOnMount !== false && isStale(query, options)))
758-
)
759-
}
760-
761757
function shouldFetchOnMount(
762758
query: Query<any, any, any, any>,
763759
options: QueryObserverOptions<any, any, any, any, any>
764760
): boolean {
765761
return (
766-
shouldLoadOnMount(query, options) || shouldRefetchOnMount(query, options)
762+
shouldLoadOnMount(query, options) ||
763+
(query.state.dataUpdatedAt > 0 &&
764+
shouldFetchOn(query, options, options.refetchOnMount))
767765
)
768766
}
769767

770-
function shouldFetchOnReconnect(
768+
function shouldFetchOn(
771769
query: Query<any, any, any, any>,
772-
options: QueryObserverOptions<any, any, any, any, any>
773-
): boolean {
774-
return (
775-
options.enabled !== false &&
776-
(options.refetchOnReconnect === 'always' ||
777-
(options.refetchOnReconnect !== false && isStale(query, options)))
778-
)
779-
}
770+
options: QueryObserverOptions<any, any, any, any, any>,
771+
field: typeof options['refetchOnMount'] &
772+
typeof options['refetchOnWindowFocus'] &
773+
typeof options['refetchOnReconnect']
774+
) {
775+
if (options.enabled !== false) {
776+
const value = typeof field === 'function' ? field(query) : field
780777

781-
function shouldFetchOnWindowFocus(
782-
query: Query<any, any, any, any>,
783-
options: QueryObserverOptions<any, any, any, any, any>
784-
): boolean {
785-
return (
786-
options.enabled !== false &&
787-
(options.refetchOnWindowFocus === 'always' ||
788-
(options.refetchOnWindowFocus !== false && isStale(query, options)))
789-
)
778+
return value === 'always' || (value !== false && isStale(query, options))
779+
}
780+
return false
790781
}
791782

792783
function shouldFetchOptionally(

src/core/types.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,41 @@ export interface QueryObserverOptions<
135135
* If set to `true`, the query will refetch on window focus if the data is stale.
136136
* If set to `false`, the query will not refetch on window focus.
137137
* If set to `'always'`, the query will always refetch on window focus.
138+
* If set to a function, the function will be executed with the latest data and query to compute the value.
138139
* Defaults to `true`.
139140
*/
140-
refetchOnWindowFocus?: boolean | 'always'
141+
refetchOnWindowFocus?:
142+
| boolean
143+
| 'always'
144+
| ((
145+
query: Query<TQueryFnData, TError, TQueryData, TQueryKey>
146+
) => boolean | 'always')
141147
/**
142148
* If set to `true`, the query will refetch on reconnect if the data is stale.
143149
* If set to `false`, the query will not refetch on reconnect.
144150
* If set to `'always'`, the query will always refetch on reconnect.
151+
* If set to a function, the function will be executed with the latest data and query to compute the value.
145152
* Defaults to `true`.
146153
*/
147-
refetchOnReconnect?: boolean | 'always'
154+
refetchOnReconnect?:
155+
| boolean
156+
| 'always'
157+
| ((
158+
query: Query<TQueryFnData, TError, TQueryData, TQueryKey>
159+
) => boolean | 'always')
148160
/**
149161
* If set to `true`, the query will refetch on mount if the data is stale.
150162
* If set to `false`, will disable additional instances of a query to trigger background refetches.
151163
* If set to `'always'`, the query will always refetch on mount.
164+
* If set to a function, the function will be executed with the latest data and query to compute the value
152165
* Defaults to `true`.
153166
*/
154-
refetchOnMount?: boolean | 'always'
167+
refetchOnMount?:
168+
| boolean
169+
| 'always'
170+
| ((
171+
query: Query<TQueryFnData, TError, TQueryData, TQueryKey>
172+
) => boolean | 'always')
155173
/**
156174
* If set to `false`, the query will not be retried on mount if it contains an error.
157175
* Defaults to `true`.

src/react/tests/useQuery.test.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,35 @@ describe('useQuery', () => {
24012401
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
24022402
})
24032403

2404+
it('should not refetch stale query on focus when `refetchOnWindowFocus` is set to a function that returns `false`', async () => {
2405+
const key = queryKey()
2406+
const states: UseQueryResult<number>[] = []
2407+
let count = 0
2408+
2409+
function Page() {
2410+
const state = useQuery(key, () => count++, {
2411+
staleTime: 0,
2412+
refetchOnWindowFocus: () => false,
2413+
})
2414+
states.push(state)
2415+
return null
2416+
}
2417+
2418+
renderWithClient(queryClient, <Page />)
2419+
2420+
await sleep(10)
2421+
2422+
act(() => {
2423+
window.dispatchEvent(new FocusEvent('focus'))
2424+
})
2425+
2426+
await sleep(10)
2427+
2428+
expect(states.length).toBe(2)
2429+
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
2430+
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
2431+
})
2432+
24042433
it('should not refetch fresh query on focus when `refetchOnWindowFocus` is set to `true`', async () => {
24052434
const key = queryKey()
24062435
const states: UseQueryResult<number>[] = []
@@ -2468,6 +2497,58 @@ describe('useQuery', () => {
24682497
expect(states[3]).toMatchObject({ data: 1, isFetching: false })
24692498
})
24702499

2500+
it('should calculate focus behaviour for `refetchOnWindowFocus` depending on function', async () => {
2501+
const key = queryKey()
2502+
const states: UseQueryResult<number>[] = []
2503+
let count = 0
2504+
2505+
function Page() {
2506+
const state = useQuery(
2507+
key,
2508+
async () => {
2509+
await sleep(1)
2510+
return count++
2511+
},
2512+
{
2513+
staleTime: 0,
2514+
retry: 0,
2515+
refetchOnWindowFocus: query => (query.state.data || 0) < 1,
2516+
}
2517+
)
2518+
states.push(state)
2519+
return null
2520+
}
2521+
2522+
renderWithClient(queryClient, <Page />)
2523+
2524+
await sleep(10)
2525+
2526+
expect(states.length).toBe(2)
2527+
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
2528+
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
2529+
2530+
act(() => {
2531+
window.dispatchEvent(new FocusEvent('focus'))
2532+
})
2533+
2534+
await sleep(10)
2535+
2536+
// refetch should happen
2537+
expect(states.length).toBe(4)
2538+
2539+
expect(states[2]).toMatchObject({ data: 0, isFetching: true })
2540+
expect(states[3]).toMatchObject({ data: 1, isFetching: false })
2541+
2542+
act(() => {
2543+
window.dispatchEvent(new FocusEvent('focus'))
2544+
})
2545+
2546+
await sleep(10)
2547+
2548+
// no more refetch now
2549+
expect(states.length).toBe(4)
2550+
})
2551+
24712552
it('should refetch fresh query when refetchOnMount is set to always', async () => {
24722553
const key = queryKey()
24732554
const states: UseQueryResult<string>[] = []

0 commit comments

Comments
 (0)