Skip to content

Commit f77f6f4

Browse files
fix(types): remove non-void constraint from queryFn result (#3666)
* fix(types): remove non-void constraint from queryFn result * test(types): remove non-void tests, and add tests for handling fetch -> promise<any> * test(types): remove ts-expect-error from query test file
1 parent 8095859 commit f77f6f4

File tree

5 files changed

+78
-104
lines changed

5 files changed

+78
-104
lines changed

src/core/tests/query.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,6 @@ describe('query', () => {
810810

811811
const observer = new QueryObserver(queryClient, {
812812
queryKey: key,
813-
// @ts-expect-error (queryFn must not return undefined)
814813
queryFn: () => undefined,
815814
retry: false,
816815
})

src/core/types.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ export type QueryKey = readonly unknown[]
1111
export type QueryFunction<
1212
T = unknown,
1313
TQueryKey extends QueryKey = QueryKey
14-
> = (
15-
context: QueryFunctionContext<TQueryKey>
16-
) => [T] extends [undefined]
17-
? never | 'queryFn must not return undefined or void'
18-
: [T] extends [void]
19-
? never | 'queryFn must not return undefined or void'
20-
: T | Promise<T>
14+
> = (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>
2115

2216
export interface QueryFunctionContext<
2317
TQueryKey extends QueryKey = QueryKey,

src/reactjs/tests/useQueries.test.tsx

Lines changed: 31 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -607,76 +607,6 @@ describe('useQueries', () => {
607607
// @ts-expect-error (Page component is not rendered)
608608
// eslint-disable-next-line
609609
function Page() {
610-
// Rejects queryFn that returns/resolved to undefined or void
611-
// @ts-expect-error (queryFn must not return undefined)
612-
useQueries({ queries: [{ queryKey: key1, queryFn: () => undefined }] })
613-
// @ts-expect-error (queryFn must not return void)
614-
// eslint-disable-next-line @typescript-eslint/no-empty-function
615-
useQueries({ queries: [{ queryKey: key1, queryFn: () => {} }] })
616-
617-
useQueries({
618-
// @ts-expect-error (queryFn must not return explicitly undefined)
619-
queries: [{ queryKey: key1, queryFn: (): undefined => undefined }],
620-
})
621-
622-
useQueries({
623-
// @ts-expect-error (queryFn must not return explicitly void)
624-
queries: [{ queryKey: key1, queryFn: (): void => undefined }],
625-
})
626-
627-
useQueries({
628-
// @ts-expect-error (queryFn must not return explicitly Promise<void>)
629-
queries: [{ queryKey: key1, queryFn: (): Promise<void> => undefined }],
630-
})
631-
632-
useQueries({
633-
queries: [
634-
// @ts-expect-error (queryFn must not return explicitly Promise<undefined>)
635-
{ queryKey: key1, queryFn: (): Promise<undefined> => undefined },
636-
],
637-
})
638-
useQueries({
639-
queries: [
640-
// @ts-expect-error (queryFn must not return Promise<undefined>)
641-
{ queryKey: key2, queryFn: () => Promise.resolve(undefined) },
642-
],
643-
})
644-
useQueries({
645-
// @ts-expect-error (queryFn must not return Promise<undefined>)
646-
queries: Array(50).map((_, i) => ({
647-
queryKey: ['key', i] as const,
648-
queryFn: () => Promise.resolve(undefined),
649-
})),
650-
})
651-
652-
// Rejects queryFn that always throws
653-
useQueries({
654-
queries: [
655-
// @ts-expect-error (queryFn must not return undefined)
656-
{
657-
queryKey: key3,
658-
queryFn: async () => {
659-
throw new Error('')
660-
},
661-
},
662-
],
663-
})
664-
665-
// Accepts queryFn that *sometimes* throws
666-
useQueries({
667-
queries: [
668-
{
669-
queryKey: key3,
670-
queryFn: async () => {
671-
if (Math.random() > 0.1) {
672-
throw new Error('')
673-
}
674-
return 'result'
675-
},
676-
},
677-
],
678-
})
679-
680610
// Array.map preserves TQueryFnData
681611
const result1 = useQueries({
682612
queries: Array(50).map((_, i) => ({
@@ -898,6 +828,37 @@ describe('useQueries', () => {
898828
someInvalidField: '',
899829
})),
900830
})
831+
832+
// field names should be enforced - array literal
833+
useQueries({
834+
queries: [
835+
{
836+
queryKey: key1,
837+
queryFn: () => 'string',
838+
// @ts-expect-error (invalidField)
839+
someInvalidField: [],
840+
},
841+
],
842+
})
843+
844+
// supports queryFn using fetch() to return Promise<any> - Array.map() result
845+
useQueries({
846+
queries: Array(50).map((_, i) => ({
847+
queryKey: ['key', i] as const,
848+
queryFn: () => fetch('return Promise<any>').then(resp => resp.json()),
849+
})),
850+
})
851+
852+
// supports queryFn using fetch() to return Promise<any> - array literal
853+
useQueries({
854+
queries: [
855+
{
856+
queryKey: key1,
857+
queryFn: () =>
858+
fetch('return Promise<any>').then(resp => resp.json()),
859+
},
860+
],
861+
})
901862
}
902863
})
903864

src/reactjs/tests/useQuery.test.tsx

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
QueryCache,
1919
QueryFunction,
2020
QueryFunctionContext,
21+
UseQueryOptions,
2122
} from '../..'
2223
import { ErrorBoundary } from 'react-error-boundary'
2324

@@ -36,22 +37,6 @@ describe('useQuery', () => {
3637
expectType<unknown>(noQueryFn.data)
3738
expectType<unknown>(noQueryFn.error)
3839

39-
// it should not be possible for queryFn to return undefined
40-
// @ts-expect-error (queryFn returns undefined)
41-
useQuery(key, () => undefined)
42-
43-
// it should not be possible for queryFn to have explicit void return type
44-
// @ts-expect-error (queryFn explicit return type is void)
45-
useQuery(key, (): void => undefined)
46-
47-
// it should not be possible for queryFn to have explicit Promise<void> return type
48-
// @ts-expect-error (queryFn explicit return type is Promise<void>)
49-
useQuery(key, (): Promise<void> => Promise.resolve())
50-
51-
// it should not be possible for queryFn to have explicit Promise<undefined> return type
52-
// @ts-expect-error (queryFn explicit return type is Promise<undefined>)
53-
useQuery(key, (): Promise<undefined> => Promise.resolve(undefined))
54-
5540
// it should infer the result type from the query function
5641
const fromQueryFn = useQuery(key, () => 'test')
5742
expectType<string | undefined>(fromQueryFn.data)
@@ -137,6 +122,49 @@ describe('useQuery', () => {
137122
queryKey: ['1'],
138123
queryFn: getMyDataStringKey,
139124
})
125+
126+
// it should handle query-functions that return Promise<any>
127+
useQuery(key, () =>
128+
fetch('return Promise<any>').then(resp => resp.json())
129+
)
130+
131+
// handles wrapped queries with custom fetcher passed as inline queryFn
132+
const useWrappedQuery = <
133+
TQueryKey extends [string, Record<string, unknown>?],
134+
TQueryFnData,
135+
TError,
136+
TData = TQueryFnData
137+
>(
138+
qk: TQueryKey,
139+
fetcher: (
140+
obj: TQueryKey[1],
141+
token: string
142+
// return type must be wrapped with TQueryFnReturn
143+
) => Promise<TQueryFnData>,
144+
options?: Omit<
145+
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
146+
'queryKey' | 'queryFn'
147+
>
148+
) => useQuery(qk, () => fetcher(qk[1], 'token'), options)
149+
const test = useWrappedQuery([''], async () => '1')
150+
expectType<string | undefined>(test.data)
151+
152+
// handles wrapped queries with custom fetcher passed directly to useQuery
153+
const useWrappedFuncStyleQuery = <
154+
TQueryKey extends [string, Record<string, unknown>?],
155+
TQueryFnData,
156+
TError,
157+
TData = TQueryFnData
158+
>(
159+
qk: TQueryKey,
160+
fetcher: () => Promise<TQueryFnData>,
161+
options?: Omit<
162+
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
163+
'queryKey' | 'queryFn'
164+
>
165+
) => useQuery(qk, fetcher, options)
166+
const testFuncStyle = useWrappedFuncStyleQuery([''], async () => true)
167+
expectType<boolean | undefined>(testFuncStyle.data)
140168
}
141169
})
142170

src/reactjs/useQueries.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ type UseQueryOptionsForUseQueries<
1717
TQueryKey extends QueryKey = QueryKey
1818
> = Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'context'>
1919

20-
type InvalidQueryFn = QueryFunction<
21-
undefined | Promise<undefined> | void | Promise<void>
22-
>
23-
2420
// Avoid TS depth-limit error in case of large array literal
2521
type MAXIMUM_DEPTH = 20
2622

@@ -44,9 +40,7 @@ type GetOptions<T> =
4440
: T extends [infer TQueryFnData]
4541
? UseQueryOptionsForUseQueries<TQueryFnData>
4642
: // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided
47-
T extends { queryFn?: InvalidQueryFn }
48-
? never | 'queryFn must not return undefined or void'
49-
: T extends {
43+
T extends {
5044
queryFn?: QueryFunction<infer TQueryFnData, infer TQueryKey>
5145
select: (data: any) => infer TData
5246
}
@@ -106,9 +100,7 @@ export type QueriesOptions<
106100
? T
107101
: // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type!
108102
// use this to infer the param types in the case of Array.map() argument
109-
T extends { queryFn: InvalidQueryFn }[]
110-
? (never | 'queryFn must not return undefined or void')[]
111-
: T extends UseQueryOptionsForUseQueries<
103+
T extends UseQueryOptionsForUseQueries<
112104
infer TQueryFnData,
113105
infer TError,
114106
infer TData,

0 commit comments

Comments
 (0)