Skip to content

Commit 56598fb

Browse files
authored
feat: better query filters (#2938)
1 parent e892557 commit 56598fb

10 files changed

Lines changed: 113 additions & 144 deletions

File tree

docs/src/pages/guides/filters.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,23 @@ A query filter is an object with certain conditions to match a query with:
1414
await queryClient.cancelQueries()
1515

1616
// Remove all inactive queries that begin with `posts` in the key
17-
queryClient.removeQueries('posts', { inactive: true })
17+
queryClient.removeQueries('posts', { type: 'inactive' })
1818

1919
// Refetch all active queries
20-
await queryClient.refetchQueries({ active: true })
20+
await queryClient.refetchQueries({ type: 'active' })
2121

2222
// Refetch all active queries that begin with `posts` in the key
23-
await queryClient.refetchQueries('posts', { active: true })
23+
await queryClient.refetchQueries('posts', { type: 'active' })
2424
```
2525

2626
A query filter object supports the following properties:
2727

2828
- `exact?: boolean`
2929
- If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed.
30-
- `active?: boolean`
31-
- When set to `true` it will match active queries.
32-
- When set to `false` it will match inactive queries.
33-
- `inactive?: boolean`
34-
- When set to `true` it will match inactive queries.
35-
- When set to `false` it will match active queries.
30+
- `type?: 'active' | 'inactive' | 'all'`
31+
- Defaults to `all`
32+
- When set to `active` it will match active queries.
33+
- When set to `inactive` it will match inactive queries.
3634
- `stale?: boolean`
3735
- When set to `true` it will match stale queries.
3836
- When set to `false` it will match fresh queries.

docs/src/pages/guides/migrating-to-react-query-4.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,51 @@ queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })
4545
```
4646

4747
> Note: There is no change in behaviour for automatically triggered fetches, e.g. because a query mounts or because of a window focus refetch.
48+
49+
### Query Filters
50+
51+
A [query filter](../guides/filters) is an object with certain conditions to match a query. Historically, the filter options have mostly been a combination of boolean flags. However, combining those flags can lead to impossible states. Specifically:
52+
53+
```
54+
active?: boolean
55+
- When set to true it will match active queries.
56+
- When set to false it will match inactive queries.
57+
inactive?: boolean
58+
- When set to true it will match inactive queries.
59+
- When set to false it will match active queries.
60+
```
61+
62+
Those flags don't work well when used together, because they are mutually exclusive. Setting `false` for both flags could match all queries, judging from the description, or no queries, which doesn't make much sense.
63+
64+
With v4, those filters have been combined into a single filter to better show the intent:
65+
66+
```diff
67+
- active?: boolean
68+
- inactive?: boolean
69+
+ type?: 'active' | 'inactive' | 'all'
70+
```
71+
72+
The filter defaults to `all`, and you can choose to only match `active` or `inactive` queries.
73+
74+
#### refetchActive / refetchInactive
75+
76+
[queryClient.invalidateQueries](../reference/QueryClient#queryclientinvalidatequeries) had two additional, similar flags:
77+
78+
```
79+
refetchActive: Boolean
80+
- Defaults to true
81+
- When set to false, queries that match the refetch predicate and are actively being rendered via useQuery and friends will NOT be refetched in the background, and only marked as invalid.
82+
refetchInactive: Boolean
83+
- Defaults to false
84+
- When set to true, queries that match the refetch predicate and are not being rendered via useQuery and friends will be both marked as invalid and also refetched in the background
85+
```
86+
87+
For the same reason, those have also been combined:
88+
89+
```diff
90+
- active?: boolean
91+
- inactive?: boolean
92+
+ refetchType?: 'active' | 'inactive' | 'all' | 'none'
93+
```
94+
95+
This flag defaults to `active` because `refetchActive` defaulted to `true`. This means we also need a way to tell `invalidateQueries` to not refetch at all, which is why a fourth option (`none`) is also allowed here.

docs/src/pages/reference/QueryClient.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -268,27 +268,26 @@ queryClient.setQueriesData(queryKey | filters, updater)
268268

269269
The `invalidateQueries` method can be used to invalidate and refetch single or multiple queries in the cache based on their query keys or any other functionally accessible property/state of the query. By default, all matching queries are immediately marked as invalid and active queries are refetched in the background.
270270

271-
- If you **do not want active queries to refetch**, and simply be marked as invalid, you can use the `refetchActive: false` option.
272-
- If you **want inactive queries to refetch** as well, use the `refetchInactive: true` option
271+
- If you **do not want active queries to refetch**, and simply be marked as invalid, you can use the `refetchType: 'none'` option.
272+
- If you **want inactive queries to refetch** as well, use the `refetchTye: 'all'` option
273273

274274
```js
275275
await queryClient.invalidateQueries('posts', {
276276
exact,
277-
refetchActive: true,
278-
refetchInactive: false
277+
refetchType: 'active',
279278
}, { throwOnError, cancelRefetch })
280279
```
281280

282281
**Options**
283282

284283
- `queryKey?: QueryKey`: [Query Keys](../guides/query-keys)
285284
- `filters?: QueryFilters`: [Query Filters](../guides/filters#query-filters)
286-
- `refetchActive: Boolean`
287-
- Defaults to `true`
288-
- When set to `false`, queries that match the refetch predicate and are actively being rendered via `useQuery` and friends will NOT be refetched in the background, and only marked as invalid.
289-
- `refetchInactive: Boolean`
290-
- Defaults to `false`
291-
- When set to `true`, queries that match the refetch predicate and are not being rendered via `useQuery` and friends will be both marked as invalid and also refetched in the background
285+
- `refetchType?: 'active' | 'inactive' | 'all' | 'none'`
286+
- Defaults to `'active'`
287+
- When set to `active`, only queries that match the refetch predicate and are actively being rendered via `useQuery` and friends will be refetched in the background.
288+
- When set to `inactive`, only queries that match the refetch predicate and are NOT actively being rendered via `useQuery` and friends will be refetched in the background.
289+
- When set to `all`, all queries that match the refetch predicate will be refetched in the background.
290+
- When set to `none`, no queries will be refetched, and those that match the refetch predicate will be marked as invalid only.
292291
- `refetchPage: (page: TData, index: number, allPages: TData[]) => boolean`
293292
- Only for [Infinite Queries](../guides/infinite-queries#refetchpage)
294293
- Use this function to specify which pages should be refetched
@@ -314,10 +313,10 @@ await queryClient.refetchQueries()
314313
await queryClient.refetchQueries({ stale: true })
315314

316315
// refetch all active queries partially matching a query key:
317-
await queryClient.refetchQueries(['posts'], { active: true })
316+
await queryClient.refetchQueries(['posts'], { type: 'active' })
318317

319318
// refetch all active queries exactly matching a query key:
320-
await queryClient.refetchQueries(['posts', 1], { active: true, exact: true })
319+
await queryClient.refetchQueries(['posts', 1], { type: 'active', exact: true })
321320
```
322321

323322
**Options**

src/core/queryClient.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ export class QueryClient {
203203
const queryCache = this.queryCache
204204

205205
const refetchFilters: RefetchQueryFilters = {
206+
type: 'active',
206207
...filters,
207-
active: true,
208208
}
209209

210210
return notifyManager.batch(() => {
@@ -255,18 +255,18 @@ export class QueryClient {
255255
): Promise<void> {
256256
const [filters, options] = parseFilterArgs(arg1, arg2, arg3)
257257

258-
const refetchFilters: RefetchQueryFilters = {
259-
...filters,
260-
// if filters.refetchActive is not provided and filters.active is explicitly false,
261-
// e.g. invalidateQueries({ active: false }), we don't want to refetch active queries
262-
active: filters.refetchActive ?? filters.active ?? true,
263-
inactive: filters.refetchInactive ?? false,
264-
}
265-
266258
return notifyManager.batch(() => {
267259
this.queryCache.findAll(filters).forEach(query => {
268260
query.invalidate()
269261
})
262+
263+
if (filters?.refetchType === 'none') {
264+
return Promise.resolve()
265+
}
266+
const refetchFilters: RefetchQueryFilters = {
267+
...filters,
268+
type: filters?.refetchType ?? filters?.type ?? 'active',
269+
}
270270
return this.refetchQueries(refetchFilters, options)
271271
})
272272
}

src/core/tests/queriesObserver.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ describe('queriesObserver', () => {
101101
observer.setQueries([{ queryKey: key2, queryFn: queryFn2 }])
102102
await sleep(1)
103103
const queryCache = queryClient.getQueryCache()
104-
expect(queryCache.find(key1, { active: true })).toBeUndefined()
105-
expect(queryCache.find(key2, { active: true })).toBeDefined()
104+
expect(queryCache.find(key1, { type: 'active' })).toBeUndefined()
105+
expect(queryCache.find(key2, { type: 'active' })).toBeDefined()
106106
unsubscribe()
107-
expect(queryCache.find(key1, { active: true })).toBeUndefined()
108-
expect(queryCache.find(key2, { active: true })).toBeUndefined()
107+
expect(queryCache.find(key1, { type: 'active' })).toBeUndefined()
108+
expect(queryCache.find(key2, { type: 'active' })).toBeUndefined()
109109
expect(results.length).toBe(6)
110110
expect(results[0]).toMatchObject([
111111
{ status: 'idle', data: undefined },

src/core/tests/queryCache.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,20 @@ describe('queryCache', () => {
9292
expect(queryCache.findAll([key1])).toEqual([query1])
9393
expect(queryCache.findAll()).toEqual([query1, query2, query3, query4])
9494
expect(queryCache.findAll({})).toEqual([query1, query2, query3, query4])
95-
expect(queryCache.findAll(key1, { active: false })).toEqual([query1])
96-
expect(queryCache.findAll(key1, { active: true })).toEqual([])
95+
expect(queryCache.findAll(key1, { type: 'inactive' })).toEqual([query1])
96+
expect(queryCache.findAll(key1, { type: 'active' })).toEqual([])
9797
expect(queryCache.findAll(key1, { stale: true })).toEqual([])
9898
expect(queryCache.findAll(key1, { stale: false })).toEqual([query1])
99-
expect(queryCache.findAll(key1, { stale: false, active: true })).toEqual(
100-
[]
101-
)
10299
expect(
103-
queryCache.findAll(key1, { stale: false, active: false })
100+
queryCache.findAll(key1, { stale: false, type: 'active' })
101+
).toEqual([])
102+
expect(
103+
queryCache.findAll(key1, { stale: false, type: 'inactive' })
104104
).toEqual([query1])
105105
expect(
106106
queryCache.findAll(key1, {
107107
stale: false,
108-
active: false,
108+
type: 'inactive',
109109
exact: true,
110110
})
111111
).toEqual([query1])
@@ -128,8 +128,8 @@ describe('queryCache', () => {
128128
query3,
129129
])
130130
expect(queryCache.findAll([{ a: 'a' }], { stale: true })).toEqual([])
131-
expect(queryCache.findAll([{ a: 'a' }], { active: true })).toEqual([])
132-
expect(queryCache.findAll([{ a: 'a' }], { inactive: true })).toEqual([
131+
expect(queryCache.findAll([{ a: 'a' }], { type: 'active' })).toEqual([])
132+
expect(queryCache.findAll([{ a: 'a' }], { type: 'inactive' })).toEqual([
133133
query3,
134134
])
135135
expect(

src/core/tests/queryClient.test.tsx

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ describe('queryClient', () => {
673673
staleTime: Infinity,
674674
})
675675
const unsubscribe = observer.subscribe()
676-
await queryClient.refetchQueries({ active: true, stale: false })
676+
await queryClient.refetchQueries({ type: 'active', stale: false })
677677
unsubscribe()
678678
expect(queryFn1).toHaveBeenCalledTimes(2)
679679
expect(queryFn2).toHaveBeenCalledTimes(1)
@@ -713,7 +713,7 @@ describe('queryClient', () => {
713713
})
714714
const unsubscribe = observer.subscribe()
715715
await queryClient.refetchQueries(
716-
{ active: true, stale: true },
716+
{ type: 'active', stale: true },
717717
{ cancelRefetch: false }
718718
)
719719
unsubscribe()
@@ -753,7 +753,7 @@ describe('queryClient', () => {
753753
staleTime: Infinity,
754754
})
755755
const unsubscribe = observer.subscribe()
756-
await queryClient.refetchQueries({ active: true, inactive: true })
756+
await queryClient.refetchQueries({ type: 'all' })
757757
unsubscribe()
758758
expect(queryFn1).toHaveBeenCalledTimes(2)
759759
expect(queryFn2).toHaveBeenCalledTimes(2)
@@ -772,7 +772,7 @@ describe('queryClient', () => {
772772
staleTime: Infinity,
773773
})
774774
const unsubscribe = observer.subscribe()
775-
await queryClient.refetchQueries({ active: true })
775+
await queryClient.refetchQueries({ type: 'active' })
776776
unsubscribe()
777777
expect(queryFn1).toHaveBeenCalledTimes(2)
778778
expect(queryFn2).toHaveBeenCalledTimes(1)
@@ -791,31 +791,12 @@ describe('queryClient', () => {
791791
staleTime: Infinity,
792792
})
793793
const unsubscribe = observer.subscribe()
794-
await queryClient.refetchQueries({ inactive: true })
794+
await queryClient.refetchQueries({ type: 'inactive' })
795795
unsubscribe()
796796
expect(queryFn1).toHaveBeenCalledTimes(1)
797797
expect(queryFn2).toHaveBeenCalledTimes(2)
798798
})
799799

800-
test('should skip refetch for all active and inactive queries', async () => {
801-
const key1 = queryKey()
802-
const key2 = queryKey()
803-
const queryFn1 = jest.fn()
804-
const queryFn2 = jest.fn()
805-
await queryClient.fetchQuery(key1, queryFn1)
806-
await queryClient.fetchQuery(key2, queryFn2)
807-
const observer = new QueryObserver(queryClient, {
808-
queryKey: key1,
809-
queryFn: queryFn1,
810-
staleTime: Infinity,
811-
})
812-
const unsubscribe = observer.subscribe()
813-
await queryClient.refetchQueries({ active: false, inactive: false })
814-
unsubscribe()
815-
expect(queryFn1).toHaveBeenCalledTimes(1)
816-
expect(queryFn2).toHaveBeenCalledTimes(1)
817-
})
818-
819800
test('should throw an error if throwOnError option is set to true', async () => {
820801
const consoleMock = mockConsoleError()
821802
const key1 = queryKey()
@@ -880,7 +861,7 @@ describe('queryClient', () => {
880861
expect(queryFn2).toHaveBeenCalledTimes(1)
881862
})
882863

883-
test('should not refetch active queries when "refetchActive" is false', async () => {
864+
test('should not refetch active queries when "refetch" is "none"', async () => {
884865
const key1 = queryKey()
885866
const key2 = queryKey()
886867
const queryFn1 = jest.fn()
@@ -894,14 +875,14 @@ describe('queryClient', () => {
894875
})
895876
const unsubscribe = observer.subscribe()
896877
queryClient.invalidateQueries(key1, {
897-
refetchActive: false,
878+
refetchType: 'none',
898879
})
899880
unsubscribe()
900881
expect(queryFn1).toHaveBeenCalledTimes(1)
901882
expect(queryFn2).toHaveBeenCalledTimes(1)
902883
})
903884

904-
test('should refetch inactive queries when "refetchInactive" is true', async () => {
885+
test('should refetch inactive queries when "refetch" is "inactive"', async () => {
905886
const key1 = queryKey()
906887
const key2 = queryKey()
907888
const queryFn1 = jest.fn()
@@ -916,14 +897,14 @@ describe('queryClient', () => {
916897
})
917898
const unsubscribe = observer.subscribe()
918899
queryClient.invalidateQueries(key1, {
919-
refetchInactive: true,
900+
refetchType: 'inactive',
920901
})
921902
unsubscribe()
922903
expect(queryFn1).toHaveBeenCalledTimes(2)
923904
expect(queryFn2).toHaveBeenCalledTimes(1)
924905
})
925906

926-
test('should not refetch active queries when "refetchActive" is not provided and "active" is false', async () => {
907+
test('should refetch active and inactive queries when "refetch" is "all"', async () => {
927908
const key1 = queryKey()
928909
const key2 = queryKey()
929910
const queryFn1 = jest.fn()
@@ -936,12 +917,12 @@ describe('queryClient', () => {
936917
staleTime: Infinity,
937918
})
938919
const unsubscribe = observer.subscribe()
939-
queryClient.invalidateQueries(key1, {
940-
active: false,
920+
queryClient.invalidateQueries({
921+
refetchType: 'all',
941922
})
942923
unsubscribe()
943-
expect(queryFn1).toHaveBeenCalledTimes(1)
944-
expect(queryFn2).toHaveBeenCalledTimes(1)
924+
expect(queryFn1).toHaveBeenCalledTimes(2)
925+
expect(queryFn2).toHaveBeenCalledTimes(2)
945926
})
946927

947928
test('should cancel ongoing fetches if cancelRefetch option is set (default value)', async () => {
@@ -1123,7 +1104,7 @@ describe('queryClient', () => {
11231104

11241105
await queryClient.invalidateQueries({
11251106
queryKey: key,
1126-
refetchInactive: true,
1107+
refetchType: 'all',
11271108
refetchPage: (page, _, allPages) => {
11281109
return page === allPages[0]
11291110
},
@@ -1155,7 +1136,7 @@ describe('queryClient', () => {
11551136

11561137
await queryClient.resetQueries({
11571138
queryKey: key,
1158-
inactive: true,
1139+
type: 'inactive',
11591140
refetchPage: (page, _, allPages) => {
11601141
return page === allPages[0]
11611142
},

0 commit comments

Comments
 (0)