Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/core/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,10 @@ export class QueryClient {

const refetchFilters: QueryFilters = {
...filters,
active: filters.refetchActive ?? true,
inactive: filters.refetchInactive,
// if filters.refetchActive is not provided and filters.active is explicitly false,
// e.g. invalidateQueries({ active: false }), we don't want to refetch active queries
active: filters.refetchActive ?? filters.active ?? true,
inactive: filters.refetchInactive ?? false,
}

return notifyManager.batch(() => {
Expand Down
124 changes: 124 additions & 0 deletions src/core/tests/queryClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -649,9 +649,85 @@ describe('queryClient', () => {
expect(queryFn1).toHaveBeenCalledTimes(2)
expect(queryFn2).toHaveBeenCalledTimes(2)
})

test('should be able to refetch only active queries', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
})
const unsubscribe = observer.subscribe()
await queryClient.refetchQueries({ active: true })
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(2)
expect(queryFn2).toHaveBeenCalledTimes(1)
})

test('should be able to refetch only inactive queries', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
})
const unsubscribe = observer.subscribe()
await queryClient.refetchQueries({ inactive: true })
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(1)
expect(queryFn2).toHaveBeenCalledTimes(2)
})

test('should skip refetch for all active and inactive queries', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
})
const unsubscribe = observer.subscribe()
await queryClient.refetchQueries({ active: false, inactive: false })
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(1)
expect(queryFn2).toHaveBeenCalledTimes(1)
})
})

describe('invalidateQueries', () => {
test('should refetch active queries by default', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
})
const unsubscribe = observer.subscribe()
queryClient.invalidateQueries(key1)
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(2)
expect(queryFn2).toHaveBeenCalledTimes(1)
})

test('should not refetch inactive queries by default', async () => {
const key1 = queryKey()
const key2 = queryKey()
Expand All @@ -673,10 +749,14 @@ describe('queryClient', () => {

test('should not refetch active queries when "refetchActive" is false', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
})
const unsubscribe = observer.subscribe()
Expand All @@ -685,6 +765,50 @@ describe('queryClient', () => {
})
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(1)
expect(queryFn2).toHaveBeenCalledTimes(1)
})

test('should refetch inactive queries when "refetchInactive" is true', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
enabled: false,
})
const unsubscribe = observer.subscribe()
queryClient.invalidateQueries(key1, {
refetchInactive: true,
})
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(2)
expect(queryFn2).toHaveBeenCalledTimes(1)
})

test('should not refetch active queries when "refetchActive" is not provided and "active" is false', async () => {
const key1 = queryKey()
const key2 = queryKey()
const queryFn1 = jest.fn()
const queryFn2 = jest.fn()
await queryClient.fetchQuery(key1, queryFn1)
await queryClient.fetchQuery(key2, queryFn2)
const observer = new QueryObserver(queryClient, {
queryKey: key1,
queryFn: queryFn1,
staleTime: Infinity,
})
const unsubscribe = observer.subscribe()
queryClient.invalidateQueries(key1, {
active: false,
})
unsubscribe()
expect(queryFn1).toHaveBeenCalledTimes(1)
expect(queryFn2).toHaveBeenCalledTimes(1)
})
})

Expand Down
27 changes: 26 additions & 1 deletion src/core/tests/utils.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { replaceEqualDeep, partialDeepEqual, isPlainObject } from '../utils'
import {
replaceEqualDeep,
partialDeepEqual,
isPlainObject,
mapQueryStatusFilter,
} from '../utils'
import { QueryClient, QueryCache, setLogger, Logger } from '../..'
import { queryKey } from '../../react/tests/utils'

Expand Down Expand Up @@ -302,4 +307,24 @@ describe('core/utils', () => {
expect(result.todos[1]).toBe(prev.todos[1])
})
})

describe('mapQueryStatusFilter', () => {
it.each`
active | inactive | statusFilter
${true} | ${true} | ${'all'}
${undefined} | ${undefined} | ${'all'}
${false} | ${false} | ${'none'}
${true} | ${false} | ${'active'}
${true} | ${undefined} | ${'active'}
${undefined} | ${false} | ${'active'}
${false} | ${true} | ${'inactive'}
${undefined} | ${true} | ${'inactive'}
${false} | ${undefined} | ${'inactive'}
`(
'returns "$statusFilter" when active is $active, and inactive is $inactive',
({ active, inactive, statusFilter }) => {
expect(mapQueryStatusFilter(active, inactive)).toBe(statusFilter)
}
)
})
})
39 changes: 31 additions & 8 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export type Updater<TInput, TOutput> =
| TOutput
| DataUpdateFunction<TInput, TOutput>

export type QueryStatusFilter = 'all' | 'active' | 'inactive' | 'none'

// UTILS

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

export function mapQueryStatusFilter(
active?: boolean,
inactive?: boolean
): QueryStatusFilter {
if (
(active === true && inactive === true) ||
(active == null && inactive == null)
) {
return 'all'
} else if (active === false && inactive === false) {
return 'none'
} else {
// At this point, active|inactive can only be true|false or false|true
// so, when only one value is provided, the missing one has to be the negated value
const isActive = active ?? !inactive
return isActive ? 'active' : 'inactive'
}
}

export function matchQuery(
filters: QueryFilters,
query: Query<any, any, any, any>
Expand All @@ -188,16 +209,18 @@ export function matchQuery(
}
}

let isActive
const queryStatusFilter = mapQueryStatusFilter(active, inactive)

if (inactive === false || (active && !inactive)) {
isActive = true
} else if (active === false || (inactive && !active)) {
isActive = false
}

if (typeof isActive === 'boolean' && query.isActive() !== isActive) {
if (queryStatusFilter === 'none') {
return false
} else if (queryStatusFilter !== 'all') {
const isActive = query.isActive()
if (queryStatusFilter === 'active' && !isActive) {
return false
}
if (queryStatusFilter === 'inactive' && isActive) {
return false
}
}

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