Skip to content

Commit e5ba8bb

Browse files
authored
fix: update matchQuery logic to cater for all permutations (#2275)
1 parent cf09241 commit e5ba8bb

File tree

4 files changed

+185
-11
lines changed

4 files changed

+185
-11
lines changed

src/core/queryClient.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,10 @@ export class QueryClient {
238238

239239
const refetchFilters: QueryFilters = {
240240
...filters,
241-
active: filters.refetchActive ?? true,
242-
inactive: filters.refetchInactive,
241+
// if filters.refetchActive is not provided and filters.active is explicitly false,
242+
// e.g. invalidateQueries({ active: false }), we don't want to refetch active queries
243+
active: filters.refetchActive ?? filters.active ?? true,
244+
inactive: filters.refetchInactive ?? false,
243245
}
244246

245247
return notifyManager.batch(() => {

src/core/tests/queryClient.test.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,85 @@ describe('queryClient', () => {
649649
expect(queryFn1).toHaveBeenCalledTimes(2)
650650
expect(queryFn2).toHaveBeenCalledTimes(2)
651651
})
652+
653+
test('should be able to refetch only active queries', async () => {
654+
const key1 = queryKey()
655+
const key2 = queryKey()
656+
const queryFn1 = jest.fn()
657+
const queryFn2 = jest.fn()
658+
await queryClient.fetchQuery(key1, queryFn1)
659+
await queryClient.fetchQuery(key2, queryFn2)
660+
const observer = new QueryObserver(queryClient, {
661+
queryKey: key1,
662+
queryFn: queryFn1,
663+
staleTime: Infinity,
664+
})
665+
const unsubscribe = observer.subscribe()
666+
await queryClient.refetchQueries({ active: true })
667+
unsubscribe()
668+
expect(queryFn1).toHaveBeenCalledTimes(2)
669+
expect(queryFn2).toHaveBeenCalledTimes(1)
670+
})
671+
672+
test('should be able to refetch only inactive queries', async () => {
673+
const key1 = queryKey()
674+
const key2 = queryKey()
675+
const queryFn1 = jest.fn()
676+
const queryFn2 = jest.fn()
677+
await queryClient.fetchQuery(key1, queryFn1)
678+
await queryClient.fetchQuery(key2, queryFn2)
679+
const observer = new QueryObserver(queryClient, {
680+
queryKey: key1,
681+
queryFn: queryFn1,
682+
staleTime: Infinity,
683+
})
684+
const unsubscribe = observer.subscribe()
685+
await queryClient.refetchQueries({ inactive: true })
686+
unsubscribe()
687+
expect(queryFn1).toHaveBeenCalledTimes(1)
688+
expect(queryFn2).toHaveBeenCalledTimes(2)
689+
})
690+
691+
test('should skip refetch for all active and inactive queries', async () => {
692+
const key1 = queryKey()
693+
const key2 = queryKey()
694+
const queryFn1 = jest.fn()
695+
const queryFn2 = jest.fn()
696+
await queryClient.fetchQuery(key1, queryFn1)
697+
await queryClient.fetchQuery(key2, queryFn2)
698+
const observer = new QueryObserver(queryClient, {
699+
queryKey: key1,
700+
queryFn: queryFn1,
701+
staleTime: Infinity,
702+
})
703+
const unsubscribe = observer.subscribe()
704+
await queryClient.refetchQueries({ active: false, inactive: false })
705+
unsubscribe()
706+
expect(queryFn1).toHaveBeenCalledTimes(1)
707+
expect(queryFn2).toHaveBeenCalledTimes(1)
708+
})
652709
})
653710

654711
describe('invalidateQueries', () => {
712+
test('should refetch active queries by default', async () => {
713+
const key1 = queryKey()
714+
const key2 = queryKey()
715+
const queryFn1 = jest.fn()
716+
const queryFn2 = jest.fn()
717+
await queryClient.fetchQuery(key1, queryFn1)
718+
await queryClient.fetchQuery(key2, queryFn2)
719+
const observer = new QueryObserver(queryClient, {
720+
queryKey: key1,
721+
queryFn: queryFn1,
722+
staleTime: Infinity,
723+
})
724+
const unsubscribe = observer.subscribe()
725+
queryClient.invalidateQueries(key1)
726+
unsubscribe()
727+
expect(queryFn1).toHaveBeenCalledTimes(2)
728+
expect(queryFn2).toHaveBeenCalledTimes(1)
729+
})
730+
655731
test('should not refetch inactive queries by default', async () => {
656732
const key1 = queryKey()
657733
const key2 = queryKey()
@@ -673,10 +749,14 @@ describe('queryClient', () => {
673749

674750
test('should not refetch active queries when "refetchActive" is false', async () => {
675751
const key1 = queryKey()
752+
const key2 = queryKey()
676753
const queryFn1 = jest.fn()
754+
const queryFn2 = jest.fn()
677755
await queryClient.fetchQuery(key1, queryFn1)
756+
await queryClient.fetchQuery(key2, queryFn2)
678757
const observer = new QueryObserver(queryClient, {
679758
queryKey: key1,
759+
queryFn: queryFn1,
680760
staleTime: Infinity,
681761
})
682762
const unsubscribe = observer.subscribe()
@@ -685,6 +765,50 @@ describe('queryClient', () => {
685765
})
686766
unsubscribe()
687767
expect(queryFn1).toHaveBeenCalledTimes(1)
768+
expect(queryFn2).toHaveBeenCalledTimes(1)
769+
})
770+
771+
test('should refetch inactive queries when "refetchInactive" is true', async () => {
772+
const key1 = queryKey()
773+
const key2 = queryKey()
774+
const queryFn1 = jest.fn()
775+
const queryFn2 = jest.fn()
776+
await queryClient.fetchQuery(key1, queryFn1)
777+
await queryClient.fetchQuery(key2, queryFn2)
778+
const observer = new QueryObserver(queryClient, {
779+
queryKey: key1,
780+
queryFn: queryFn1,
781+
staleTime: Infinity,
782+
enabled: false,
783+
})
784+
const unsubscribe = observer.subscribe()
785+
queryClient.invalidateQueries(key1, {
786+
refetchInactive: true,
787+
})
788+
unsubscribe()
789+
expect(queryFn1).toHaveBeenCalledTimes(2)
790+
expect(queryFn2).toHaveBeenCalledTimes(1)
791+
})
792+
793+
test('should not refetch active queries when "refetchActive" is not provided and "active" is false', async () => {
794+
const key1 = queryKey()
795+
const key2 = queryKey()
796+
const queryFn1 = jest.fn()
797+
const queryFn2 = jest.fn()
798+
await queryClient.fetchQuery(key1, queryFn1)
799+
await queryClient.fetchQuery(key2, queryFn2)
800+
const observer = new QueryObserver(queryClient, {
801+
queryKey: key1,
802+
queryFn: queryFn1,
803+
staleTime: Infinity,
804+
})
805+
const unsubscribe = observer.subscribe()
806+
queryClient.invalidateQueries(key1, {
807+
active: false,
808+
})
809+
unsubscribe()
810+
expect(queryFn1).toHaveBeenCalledTimes(1)
811+
expect(queryFn2).toHaveBeenCalledTimes(1)
688812
})
689813
})
690814

src/core/tests/utils.test.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { replaceEqualDeep, partialDeepEqual, isPlainObject } from '../utils'
1+
import {
2+
replaceEqualDeep,
3+
partialDeepEqual,
4+
isPlainObject,
5+
mapQueryStatusFilter,
6+
} from '../utils'
27
import { QueryClient, QueryCache, setLogger, Logger } from '../..'
38
import { queryKey } from '../../react/tests/utils'
49

@@ -302,4 +307,24 @@ describe('core/utils', () => {
302307
expect(result.todos[1]).toBe(prev.todos[1])
303308
})
304309
})
310+
311+
describe('mapQueryStatusFilter', () => {
312+
it.each`
313+
active | inactive | statusFilter
314+
${true} | ${true} | ${'all'}
315+
${undefined} | ${undefined} | ${'all'}
316+
${false} | ${false} | ${'none'}
317+
${true} | ${false} | ${'active'}
318+
${true} | ${undefined} | ${'active'}
319+
${undefined} | ${false} | ${'active'}
320+
${false} | ${true} | ${'inactive'}
321+
${undefined} | ${true} | ${'inactive'}
322+
${false} | ${undefined} | ${'inactive'}
323+
`(
324+
'returns "$statusFilter" when active is $active, and inactive is $inactive',
325+
({ active, inactive, statusFilter }) => {
326+
expect(mapQueryStatusFilter(active, inactive)).toBe(statusFilter)
327+
}
328+
)
329+
})
305330
})

src/core/utils.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export type Updater<TInput, TOutput> =
6868
| TOutput
6969
| DataUpdateFunction<TInput, TOutput>
7070

71+
export type QueryStatusFilter = 'all' | 'active' | 'inactive' | 'none'
72+
7173
// UTILS
7274

7375
export const isServer = typeof window === 'undefined'
@@ -164,6 +166,25 @@ export function parseFilterArgs<
164166
: [arg1 || {}, arg2]) as [TFilters, TOptions]
165167
}
166168

169+
export function mapQueryStatusFilter(
170+
active?: boolean,
171+
inactive?: boolean
172+
): QueryStatusFilter {
173+
if (
174+
(active === true && inactive === true) ||
175+
(active == null && inactive == null)
176+
) {
177+
return 'all'
178+
} else if (active === false && inactive === false) {
179+
return 'none'
180+
} else {
181+
// At this point, active|inactive can only be true|false or false|true
182+
// so, when only one value is provided, the missing one has to be the negated value
183+
const isActive = active ?? !inactive
184+
return isActive ? 'active' : 'inactive'
185+
}
186+
}
187+
167188
export function matchQuery(
168189
filters: QueryFilters,
169190
query: Query<any, any, any, any>
@@ -188,16 +209,18 @@ export function matchQuery(
188209
}
189210
}
190211

191-
let isActive
212+
const queryStatusFilter = mapQueryStatusFilter(active, inactive)
192213

193-
if (inactive === false || (active && !inactive)) {
194-
isActive = true
195-
} else if (active === false || (inactive && !active)) {
196-
isActive = false
197-
}
198-
199-
if (typeof isActive === 'boolean' && query.isActive() !== isActive) {
214+
if (queryStatusFilter === 'none') {
200215
return false
216+
} else if (queryStatusFilter !== 'all') {
217+
const isActive = query.isActive()
218+
if (queryStatusFilter === 'active' && !isActive) {
219+
return false
220+
}
221+
if (queryStatusFilter === 'inactive' && isActive) {
222+
return false
223+
}
201224
}
202225

203226
if (typeof stale === 'boolean' && query.isStale() !== stale) {

0 commit comments

Comments
 (0)