diff --git a/src/core/tests/query.test.tsx b/src/core/tests/query.test.tsx index dee95ba640..3206665b57 100644 --- a/src/core/tests/query.test.tsx +++ b/src/core/tests/query.test.tsx @@ -810,7 +810,6 @@ describe('query', () => { const observer = new QueryObserver(queryClient, { queryKey: key, - // @ts-expect-error (queryFn must not return undefined) queryFn: () => undefined, retry: false, }) diff --git a/src/core/types.ts b/src/core/types.ts index dde435dc84..41831af20a 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -11,13 +11,7 @@ export type QueryKey = readonly unknown[] export type QueryFunction< T = unknown, TQueryKey extends QueryKey = QueryKey -> = ( - context: QueryFunctionContext -) => [T] extends [undefined] - ? never | 'queryFn must not return undefined or void' - : [T] extends [void] - ? never | 'queryFn must not return undefined or void' - : T | Promise +> = (context: QueryFunctionContext) => T | Promise export interface QueryFunctionContext< TQueryKey extends QueryKey = QueryKey, diff --git a/src/reactjs/tests/useQueries.test.tsx b/src/reactjs/tests/useQueries.test.tsx index f4499eeb31..6b9ea7afb4 100644 --- a/src/reactjs/tests/useQueries.test.tsx +++ b/src/reactjs/tests/useQueries.test.tsx @@ -607,76 +607,6 @@ describe('useQueries', () => { // @ts-expect-error (Page component is not rendered) // eslint-disable-next-line function Page() { - // Rejects queryFn that returns/resolved to undefined or void - // @ts-expect-error (queryFn must not return undefined) - useQueries({ queries: [{ queryKey: key1, queryFn: () => undefined }] }) - // @ts-expect-error (queryFn must not return void) - // eslint-disable-next-line @typescript-eslint/no-empty-function - useQueries({ queries: [{ queryKey: key1, queryFn: () => {} }] }) - - useQueries({ - // @ts-expect-error (queryFn must not return explicitly undefined) - queries: [{ queryKey: key1, queryFn: (): undefined => undefined }], - }) - - useQueries({ - // @ts-expect-error (queryFn must not return explicitly void) - queries: [{ queryKey: key1, queryFn: (): void => undefined }], - }) - - useQueries({ - // @ts-expect-error (queryFn must not return explicitly Promise) - queries: [{ queryKey: key1, queryFn: (): Promise => undefined }], - }) - - useQueries({ - queries: [ - // @ts-expect-error (queryFn must not return explicitly Promise) - { queryKey: key1, queryFn: (): Promise => undefined }, - ], - }) - useQueries({ - queries: [ - // @ts-expect-error (queryFn must not return Promise) - { queryKey: key2, queryFn: () => Promise.resolve(undefined) }, - ], - }) - useQueries({ - // @ts-expect-error (queryFn must not return Promise) - queries: Array(50).map((_, i) => ({ - queryKey: ['key', i] as const, - queryFn: () => Promise.resolve(undefined), - })), - }) - - // Rejects queryFn that always throws - useQueries({ - queries: [ - // @ts-expect-error (queryFn must not return undefined) - { - queryKey: key3, - queryFn: async () => { - throw new Error('') - }, - }, - ], - }) - - // Accepts queryFn that *sometimes* throws - useQueries({ - queries: [ - { - queryKey: key3, - queryFn: async () => { - if (Math.random() > 0.1) { - throw new Error('') - } - return 'result' - }, - }, - ], - }) - // Array.map preserves TQueryFnData const result1 = useQueries({ queries: Array(50).map((_, i) => ({ @@ -898,6 +828,37 @@ describe('useQueries', () => { someInvalidField: '', })), }) + + // field names should be enforced - array literal + useQueries({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + // @ts-expect-error (invalidField) + someInvalidField: [], + }, + ], + }) + + // supports queryFn using fetch() to return Promise - Array.map() result + useQueries({ + queries: Array(50).map((_, i) => ({ + queryKey: ['key', i] as const, + queryFn: () => fetch('return Promise').then(resp => resp.json()), + })), + }) + + // supports queryFn using fetch() to return Promise - array literal + useQueries({ + queries: [ + { + queryKey: key1, + queryFn: () => + fetch('return Promise').then(resp => resp.json()), + }, + ], + }) } }) diff --git a/src/reactjs/tests/useQuery.test.tsx b/src/reactjs/tests/useQuery.test.tsx index 19742d6f6a..c53d92aa24 100644 --- a/src/reactjs/tests/useQuery.test.tsx +++ b/src/reactjs/tests/useQuery.test.tsx @@ -18,6 +18,7 @@ import { QueryCache, QueryFunction, QueryFunctionContext, + UseQueryOptions, } from '../..' import { ErrorBoundary } from 'react-error-boundary' @@ -36,22 +37,6 @@ describe('useQuery', () => { expectType(noQueryFn.data) expectType(noQueryFn.error) - // it should not be possible for queryFn to return undefined - // @ts-expect-error (queryFn returns undefined) - useQuery(key, () => undefined) - - // it should not be possible for queryFn to have explicit void return type - // @ts-expect-error (queryFn explicit return type is void) - useQuery(key, (): void => undefined) - - // it should not be possible for queryFn to have explicit Promise return type - // @ts-expect-error (queryFn explicit return type is Promise) - useQuery(key, (): Promise => Promise.resolve()) - - // it should not be possible for queryFn to have explicit Promise return type - // @ts-expect-error (queryFn explicit return type is Promise) - useQuery(key, (): Promise => Promise.resolve(undefined)) - // it should infer the result type from the query function const fromQueryFn = useQuery(key, () => 'test') expectType(fromQueryFn.data) @@ -137,6 +122,49 @@ describe('useQuery', () => { queryKey: ['1'], queryFn: getMyDataStringKey, }) + + // it should handle query-functions that return Promise + useQuery(key, () => + fetch('return Promise').then(resp => resp.json()) + ) + + // handles wrapped queries with custom fetcher passed as inline queryFn + const useWrappedQuery = < + TQueryKey extends [string, Record?], + TQueryFnData, + TError, + TData = TQueryFnData + >( + qk: TQueryKey, + fetcher: ( + obj: TQueryKey[1], + token: string + // return type must be wrapped with TQueryFnReturn + ) => Promise, + options?: Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' + > + ) => useQuery(qk, () => fetcher(qk[1], 'token'), options) + const test = useWrappedQuery([''], async () => '1') + expectType(test.data) + + // handles wrapped queries with custom fetcher passed directly to useQuery + const useWrappedFuncStyleQuery = < + TQueryKey extends [string, Record?], + TQueryFnData, + TError, + TData = TQueryFnData + >( + qk: TQueryKey, + fetcher: () => Promise, + options?: Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' + > + ) => useQuery(qk, fetcher, options) + const testFuncStyle = useWrappedFuncStyleQuery([''], async () => true) + expectType(testFuncStyle.data) } }) diff --git a/src/reactjs/useQueries.ts b/src/reactjs/useQueries.ts index f666719dd0..9d2065a86d 100644 --- a/src/reactjs/useQueries.ts +++ b/src/reactjs/useQueries.ts @@ -17,10 +17,6 @@ type UseQueryOptionsForUseQueries< TQueryKey extends QueryKey = QueryKey > = Omit, 'context'> -type InvalidQueryFn = QueryFunction< - undefined | Promise | void | Promise -> - // Avoid TS depth-limit error in case of large array literal type MAXIMUM_DEPTH = 20 @@ -44,9 +40,7 @@ type GetOptions = : T extends [infer TQueryFnData] ? UseQueryOptionsForUseQueries : // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided - T extends { queryFn?: InvalidQueryFn } - ? never | 'queryFn must not return undefined or void' - : T extends { + T extends { queryFn?: QueryFunction select: (data: any) => infer TData } @@ -106,9 +100,7 @@ export type QueriesOptions< ? T : // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type! // use this to infer the param types in the case of Array.map() argument - T extends { queryFn: InvalidQueryFn }[] - ? (never | 'queryFn must not return undefined or void')[] - : T extends UseQueryOptionsForUseQueries< + T extends UseQueryOptionsForUseQueries< infer TQueryFnData, infer TError, infer TData,