Skip to content

Commit 0381b95

Browse files
committed
throw error if enabled: true/skiptoken and no cached data
1 parent e7db184 commit 0381b95

File tree

4 files changed

+174
-0
lines changed

4 files changed

+174
-0
lines changed

packages/query-core/src/__tests__/queryClient.test-d.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assertType, describe, expectTypeOf, it } from 'vitest'
22
import { QueryClient } from '../queryClient'
3+
import { skipToken } from '../utils'
34
import type { MutationFilters, QueryFilters, Updater } from '../utils'
45
import type { Mutation } from '../mutation'
56
import type { Query, QueryState } from '../query'
@@ -236,6 +237,37 @@ describe('query', () => {
236237

237238
expectTypeOf(options).toEqualTypeOf<Promise<number>>()
238239
})
240+
241+
it('should infer select type with skipToken queryFn', () => {
242+
const options = new QueryClient().query({
243+
queryKey: ['key'],
244+
queryFn: skipToken,
245+
select: (data: string) => data.length,
246+
})
247+
248+
expectTypeOf(options).toEqualTypeOf<Promise<number>>()
249+
})
250+
251+
it('should infer select type with skipToken queryFn and enabled false', () => {
252+
const options = new QueryClient().query({
253+
queryKey: ['key'],
254+
queryFn: skipToken,
255+
enabled: false,
256+
select: (data: string) => data.length,
257+
})
258+
259+
expectTypeOf(options).toEqualTypeOf<Promise<number>>()
260+
})
261+
262+
it('should infer select type with skipToken queryFn and enabled true', () => {
263+
const options = new QueryClient().query({
264+
queryKey: ['key'],
265+
enabled: false,
266+
select: (data: string) => data.length,
267+
})
268+
269+
expectTypeOf(options).toEqualTypeOf<Promise<number>>()
270+
})
239271
})
240272

241273
describe('infiniteQuery', () => {

packages/query-core/src/__tests__/queryClient.test.tsx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,123 @@ describe('queryClient', () => {
978978
expect(second).toBe(first)
979979
})
980980

981+
test('should throw when disabled and no cached data exists', async () => {
982+
const key = queryKey()
983+
const queryFn = vi.fn(() => Promise.resolve('data'))
984+
985+
await expect(
986+
queryClient.query({
987+
queryKey: key,
988+
queryFn,
989+
enabled: false,
990+
}),
991+
).rejects.toThrowError()
992+
993+
expect(queryFn).not.toHaveBeenCalled()
994+
})
995+
996+
test('should return cached data when disabled and apply select', async () => {
997+
const key = queryKey()
998+
const queryFn = vi.fn(() => Promise.resolve('fetched-data'))
999+
1000+
queryClient.setQueryData(key, 'cached-data')
1001+
1002+
const result = await queryClient.query({
1003+
queryKey: key,
1004+
queryFn,
1005+
enabled: false,
1006+
staleTime: 0,
1007+
select: (data) => `${data}-selected`,
1008+
})
1009+
1010+
expect(result).toBe('cached-data-selected')
1011+
expect(queryFn).not.toHaveBeenCalled()
1012+
})
1013+
1014+
test('should throw when skipToken is provided and no cached data exists', async () => {
1015+
const key = queryKey()
1016+
const select = vi.fn((data: unknown) => (data as string).length)
1017+
1018+
await expect(
1019+
queryClient.query({
1020+
queryKey: key,
1021+
queryFn: skipToken,
1022+
select,
1023+
}),
1024+
).rejects.toThrowError()
1025+
1026+
expect(select).not.toHaveBeenCalled()
1027+
})
1028+
1029+
test('should return cached data when skipToken is provided', async () => {
1030+
const key = queryKey()
1031+
1032+
queryClient.setQueryData(key, 'cached-data')
1033+
1034+
const result = await queryClient.query({
1035+
queryKey: key,
1036+
queryFn: skipToken,
1037+
select: (data: unknown) => (data as string).length,
1038+
})
1039+
1040+
expect(result).toBe('cached-data'.length)
1041+
})
1042+
1043+
test('should return cached data when skipToken and enabled false are both provided', async () => {
1044+
const key = queryKey()
1045+
1046+
queryClient.setQueryData(key, { value: 'cached-data' })
1047+
1048+
const result = await queryClient.query({
1049+
queryKey: key,
1050+
queryFn: skipToken,
1051+
enabled: false,
1052+
select: (data: { value: string }) => data.value.toUpperCase(),
1053+
})
1054+
1055+
expect(result).toBe('CACHED-DATA')
1056+
})
1057+
1058+
test('should throw when enabled resolves true and skipToken are provided with no cached data', async () => {
1059+
await expect(
1060+
queryClient.query({
1061+
queryKey: queryKey(),
1062+
queryFn: skipToken,
1063+
enabled: true,
1064+
}),
1065+
).rejects.toThrowError()
1066+
})
1067+
1068+
test('should return cached data when enabled resolves false and skipToken are provided', async () => {
1069+
const key1 = queryKey()
1070+
queryClient.setQueryData(key1, { value: 'cached-data' })
1071+
1072+
const booleanDisabledResult = await queryClient.query({
1073+
queryKey: key1,
1074+
queryFn: skipToken,
1075+
enabled: false,
1076+
select: (data: { value: string }) => data.value.length,
1077+
})
1078+
1079+
expect(booleanDisabledResult).toBe('cached-data'.length)
1080+
})
1081+
1082+
test('should return cached data when enabled callback returns false even if queryFn would return different data', async () => {
1083+
const key = queryKey()
1084+
const queryFn = vi.fn(() => Promise.resolve('fetched-data'))
1085+
1086+
queryClient.setQueryData(key, 'cached-data')
1087+
1088+
const result = await queryClient.query({
1089+
queryKey: key,
1090+
queryFn,
1091+
enabled: () => false,
1092+
})
1093+
1094+
expect(result).toBe('cached-data')
1095+
expect(queryFn).not.toHaveBeenCalled()
1096+
})
1097+
9811098
test('should read from cache with static staleTime even if invalidated', async () => {
9821099
const key = queryKey()
9831100

packages/query-core/src/queryClient.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
hashQueryKeyByOptions,
55
noop,
66
partialMatchKey,
7+
resolveEnabled,
78
resolveStaleTime,
89
skipToken,
910
} from './utils'
@@ -365,6 +366,25 @@ export class QueryClient {
365366
}
366367

367368
const query = this.#queryCache.build(this, defaultedOptions)
369+
const isEnabled = resolveEnabled(defaultedOptions.enabled, query) !== false
370+
371+
if (!isEnabled) {
372+
const queryData = query.state.data
373+
374+
if (queryData === undefined) {
375+
throw new Error(
376+
`Missing query data for disabled query. Query hash: '${query.queryHash}'`,
377+
)
378+
}
379+
380+
const select = defaultedOptions.select
381+
382+
if (select) {
383+
return select(queryData)
384+
}
385+
386+
return queryData as unknown as TData
387+
}
368388

369389
const isStale = query.isStaleByTime(
370390
resolveStaleTime(defaultedOptions.staleTime, query),

packages/query-core/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,11 @@ export interface QueryExecuteOptions<
502502
'queryKey'
503503
> {
504504
initialPageParam?: never
505+
/**
506+
* Set this to `false` or a function that returns `false` to disable fetching.
507+
* If cached data exists, it will be returned.
508+
*/
509+
enabled?: Enabled<TQueryFnData, TError, TQueryData, TQueryKey>
505510
select?: (data: TQueryData) => TData
506511
/**
507512
* The time in milliseconds after data is considered stale.

0 commit comments

Comments
 (0)