From a991d922e84bcf9ac46b7e23f0f72902166e55cf Mon Sep 17 00:00:00 2001 From: Gwansik Kim Date: Sat, 12 Oct 2024 03:30:05 +0900 Subject: [PATCH] refactor(types): throw type error when `skipToken` is present in suspense query (#8082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: handle type error when skipToken is present in suspense query * test(react-query): add skipToken test for query hooks * fix: merge form main * ci: apply automated fixes * feat(react-query): display “skipToken is not allowed” message in suspense hooks * feat(react-query): display “skipToken is not allowed” message in suspense hooks * feat(react-query): display “skipToken is not allowed” message in suspense hooks * fix(react-query): update error message in useSuspenseInfiniteQuery * test(react-query): add test case --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Dominik Dorfmeister --- .../__tests__/infiniteQueryOptions.test-d.tsx | 14 ++++- .../src/__tests__/prefetch.test.tsx | 10 +++- .../src/__tests__/queryOptions.test-d.tsx | 10 ++++ .../src/__tests__/suspense.test-d.tsx | 40 +++++++++---- .../__tests__/useSuspenseQueries.test-d.tsx | 59 +++++++++++++------ packages/react-query/src/index.ts | 2 + .../react-query/src/infiniteQueryOptions.ts | 56 ++++++++++++++++++ packages/react-query/src/queryOptions.ts | 28 +++++++++ packages/react-query/src/types.ts | 26 ++++++-- .../src/useSuspenseInfiniteQuery.ts | 2 +- packages/react-query/src/useSuspenseQuery.ts | 2 +- 11 files changed, 210 insertions(+), 39 deletions(-) diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx index fd8bd57917..aa706e71d3 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -1,5 +1,5 @@ import { describe, expectTypeOf, it, test } from 'vitest' -import { QueryClient, dataTagSymbol } from '@tanstack/query-core' +import { QueryClient, dataTagSymbol, skipToken } from '@tanstack/query-core' import { infiniteQueryOptions } from '../infiniteQueryOptions' import { useInfiniteQuery } from '../useInfiniteQuery' import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' @@ -134,6 +134,18 @@ describe('infiniteQueryOptions', () => { InfiniteData | undefined >() }) + it('should throw a type error when using queryFn with skipToken in a suspense query', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: + Math.random() > 0.5 ? skipToken : () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + // @ts-expect-error TS2345 + const { data } = useSuspenseInfiniteQuery(options) + expectTypeOf(data).toEqualTypeOf>() + }) test('should not be allowed to be passed to non-infinite query functions', () => { const queryClient = new QueryClient() diff --git a/packages/react-query/src/__tests__/prefetch.test.tsx b/packages/react-query/src/__tests__/prefetch.test.tsx index fc8ed8c08b..06c39af6c7 100644 --- a/packages/react-query/src/__tests__/prefetch.test.tsx +++ b/packages/react-query/src/__tests__/prefetch.test.tsx @@ -12,7 +12,11 @@ import { } from '..' import { createQueryClient, queryKey, renderWithClient, sleep } from './utils' -import type { InfiniteData, UseInfiniteQueryOptions, UseQueryOptions } from '..' +import type { + InfiniteData, + UseSuspenseInfiniteQueryOptions, + UseSuspenseQueryOptions, +} from '..' import type { Mock } from 'vitest' const generateQueryFn = (data: string) => @@ -56,7 +60,7 @@ describe('usePrefetchQuery', () => { const queryClient = createQueryClient({ queryCache }) function Suspended(props: { - queryOpts: UseQueryOptions> + queryOpts: UseSuspenseQueryOptions> children?: React.ReactNode }) { const state = useSuspenseQuery(props.queryOpts) @@ -303,7 +307,7 @@ describe('usePrefetchInfiniteQuery', () => { const Fallback = vi.fn().mockImplementation(() =>
Loading...
) function Suspended(props: { - queryOpts: UseInfiniteQueryOptions< + queryOpts: UseSuspenseInfiniteQueryOptions< T, Error, InfiniteData, diff --git a/packages/react-query/src/__tests__/queryOptions.test-d.tsx b/packages/react-query/src/__tests__/queryOptions.test-d.tsx index f833d0db5a..7cacef246b 100644 --- a/packages/react-query/src/__tests__/queryOptions.test-d.tsx +++ b/packages/react-query/src/__tests__/queryOptions.test-d.tsx @@ -177,6 +177,16 @@ describe('queryOptions', () => { expectTypeOf(data).toEqualTypeOf() }) + it('should throw a type error when using queryFn with skipToken in a suspense query', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }) + // @ts-expect-error TS2345 + const { data } = useSuspenseQuery(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should return the proper type when passed to QueriesObserver', () => { const options = queryOptions({ queryKey: ['key'], diff --git a/packages/react-query/src/__tests__/suspense.test-d.tsx b/packages/react-query/src/__tests__/suspense.test-d.tsx index 6d333a817a..7d14401887 100644 --- a/packages/react-query/src/__tests__/suspense.test-d.tsx +++ b/packages/react-query/src/__tests__/suspense.test-d.tsx @@ -1,7 +1,7 @@ import { describe, expectTypeOf, it } from 'vitest' +import { skipToken } from '@tanstack/query-core' import { useSuspenseQuery } from '../useSuspenseQuery' import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' -import type { UseSuspenseQueryOptions } from '..' import type { InfiniteData } from '@tanstack/query-core' describe('useSuspenseQuery', () => { @@ -23,6 +23,20 @@ describe('useSuspenseQuery', () => { expectTypeOf(status).toEqualTypeOf<'error' | 'success'>() }) + it('should not allow skipToken in queryFn', () => { + useSuspenseQuery({ + queryKey: ['key'], + // @ts-expect-error + queryFn: skipToken, + }) + + useSuspenseQuery({ + queryKey: ['key'], + // @ts-expect-error + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }) + }) + it('should not allow placeholderData, enabled or throwOnError props', () => { useSuspenseQuery({ queryKey: ['key'], @@ -70,6 +84,20 @@ describe('useSuspenseInfiniteQuery', () => { expectTypeOf(data).toEqualTypeOf>() }) + it('should not allow skipToken in queryFn', () => { + useSuspenseInfiniteQuery({ + queryKey: ['key'], + // @ts-expect-error + queryFn: skipToken, + }) + + useSuspenseInfiniteQuery({ + queryKey: ['key'], + // @ts-expect-error + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }) + }) + it('should not have pending status', () => { const { status } = useSuspenseInfiniteQuery({ queryKey: ['key'], @@ -122,14 +150,4 @@ describe('useSuspenseInfiniteQuery', () => { // @ts-expect-error TS2339 query.isPlaceholderData }) - - it('should not accept skipToken type for queryFn in useSuspenseQuery', () => { - const query: UseSuspenseQueryOptions = { - // @ts-expect-error - queryFn: skipToken, - queryKey: [1], - } - - return query - }) }) diff --git a/packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx b/packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx index dff0829282..a7770fdf57 100644 --- a/packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx +++ b/packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx @@ -89,6 +89,47 @@ describe('UseSuspenseQueries config object overload', () => { expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() }) + it('should not allow skipToken in queryFn', () => { + useSuspenseQueries({ + queries: [ + { + queryKey: ['key'], + // @ts-expect-error + queryFn: skipToken, + }, + ], + }) + + useSuspenseQueries({ + queries: [ + { + queryKey: ['key'], + // @ts-expect-error + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }, + ], + }) + }) + + it('TData should have correct type when conditional skipToken is passed', () => { + const queryResults = useSuspenseQueries({ + queries: [ + { + queryKey: ['withSkipToken'], + // @ts-expect-error + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }, + ], + }) + + const firstResult = queryResults[0] + + expectTypeOf(firstResult).toEqualTypeOf< + UseSuspenseQueryResult + >() + expectTypeOf(firstResult.data).toEqualTypeOf() + }) + describe('custom hook', () => { it('should allow custom hooks using UseQueryOptions', () => { type Data = string @@ -113,22 +154,4 @@ describe('UseSuspenseQueries config object overload', () => { expectTypeOf(data).toEqualTypeOf() }) }) - - it('TData should have correct type when conditional skipToken is passed', () => { - const queryResults = useSuspenseQueries({ - queries: [ - { - queryKey: ['withSkipToken'], - queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), - }, - ], - }) - - const firstResult = queryResults[0] - - expectTypeOf(firstResult).toEqualTypeOf< - UseSuspenseQueryResult - >() - expectTypeOf(firstResult.data).toEqualTypeOf() - }) }) diff --git a/packages/react-query/src/index.ts b/packages/react-query/src/index.ts index 7d4724bd97..ee14798ba8 100644 --- a/packages/react-query/src/index.ts +++ b/packages/react-query/src/index.ts @@ -21,11 +21,13 @@ export { queryOptions } from './queryOptions' export type { DefinedInitialDataOptions, UndefinedInitialDataOptions, + UnusedSkipTokenOptions, } from './queryOptions' export { infiniteQueryOptions } from './infiniteQueryOptions' export type { DefinedInitialDataInfiniteOptions, UndefinedInitialDataInfiniteOptions, + UnusedSkipTokenInfiniteOptions, } from './infiniteQueryOptions' export { QueryClientContext, diff --git a/packages/react-query/src/infiniteQueryOptions.ts b/packages/react-query/src/infiniteQueryOptions.ts index 134cbabcec..e013c1f8d3 100644 --- a/packages/react-query/src/infiniteQueryOptions.ts +++ b/packages/react-query/src/infiniteQueryOptions.ts @@ -3,7 +3,9 @@ import type { DefaultError, InfiniteData, InitialDataFunction, + OmitKeyof, QueryKey, + SkipToken, } from '@tanstack/query-core' import type { UseInfiniteQueryOptions } from './types' @@ -29,6 +31,36 @@ export type UndefinedInitialDataInfiniteOptions< > } +export type UnusedSkipTokenInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = OmitKeyof< + UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey, + TPageParam + >, + 'queryFn' +> & { + queryFn: Exclude< + UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey, + TPageParam + >['queryFn'], + SkipToken + > +} + type NonUndefinedGuard = T extends undefined ? never : T export type DefinedInitialDataInfiniteOptions< @@ -75,6 +107,30 @@ export function infiniteQueryOptions< queryKey: DataTag> } +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UnusedSkipTokenInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): UnusedSkipTokenInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & { + queryKey: DataTag> +} + export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index b659cc034f..a763ab2289 100644 --- a/packages/react-query/src/queryOptions.ts +++ b/packages/react-query/src/queryOptions.ts @@ -2,7 +2,9 @@ import type { DataTag, DefaultError, InitialDataFunction, + OmitKeyof, QueryKey, + SkipToken, } from '@tanstack/query-core' import type { UseQueryOptions } from './types' @@ -18,6 +20,21 @@ export type UndefinedInitialDataOptions< | NonUndefinedGuard } +export type UnusedSkipTokenOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = OmitKeyof< + UseQueryOptions, + 'queryFn' +> & { + queryFn: Exclude< + UseQueryOptions['queryFn'], + SkipToken + > +} + type NonUndefinedGuard = T extends undefined ? never : T export type DefinedInitialDataOptions< @@ -42,6 +59,17 @@ export function queryOptions< queryKey: DataTag } +export function queryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: UnusedSkipTokenOptions, +): UnusedSkipTokenOptions & { + queryKey: DataTag +} + export function queryOptions< TQueryFnData = unknown, TError = DefaultError, diff --git a/packages/react-query/src/types.ts b/packages/react-query/src/types.ts index 59a57e728c..45da8a6a93 100644 --- a/packages/react-query/src/types.ts +++ b/packages/react-query/src/types.ts @@ -14,6 +14,7 @@ import type { QueryKey, QueryObserverOptions, QueryObserverResult, + SkipToken, } from '@tanstack/query-core' export interface UseBaseQueryOptions< @@ -47,8 +48,13 @@ export interface UseSuspenseQueryOptions< TQueryKey extends QueryKey = QueryKey, > extends OmitKeyof< UseQueryOptions, - 'enabled' | 'throwOnError' | 'placeholderData' - > {} + 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' + > { + queryFn: Exclude< + UseQueryOptions['queryFn'], + SkipToken + > +} export interface UseInfiniteQueryOptions< TQueryFnData = unknown, @@ -85,8 +91,20 @@ export interface UseSuspenseInfiniteQueryOptions< TQueryKey, TPageParam >, - 'enabled' | 'throwOnError' | 'placeholderData' - > {} + 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' + > { + queryFn: Exclude< + UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + >['queryFn'], + SkipToken + > +} export type UseBaseQueryResult< TData = unknown, diff --git a/packages/react-query/src/useSuspenseInfiniteQuery.ts b/packages/react-query/src/useSuspenseInfiniteQuery.ts index 9f922c6c30..916e89265a 100644 --- a/packages/react-query/src/useSuspenseInfiniteQuery.ts +++ b/packages/react-query/src/useSuspenseInfiniteQuery.ts @@ -33,7 +33,7 @@ export function useSuspenseInfiniteQuery< queryClient?: QueryClient, ): UseSuspenseInfiniteQueryResult { if (process.env.NODE_ENV !== 'production') { - if (options.queryFn === skipToken) { + if ((options.queryFn as any) === skipToken) { console.error('skipToken is not allowed for useSuspenseInfiniteQuery') } } diff --git a/packages/react-query/src/useSuspenseQuery.ts b/packages/react-query/src/useSuspenseQuery.ts index 9bc53e6fd6..7dfdb06477 100644 --- a/packages/react-query/src/useSuspenseQuery.ts +++ b/packages/react-query/src/useSuspenseQuery.ts @@ -15,7 +15,7 @@ export function useSuspenseQuery< queryClient?: QueryClient, ): UseSuspenseQueryResult { if (process.env.NODE_ENV !== 'production') { - if (options.queryFn === skipToken) { + if ((options.queryFn as any) === skipToken) { console.error('skipToken is not allowed for useSuspenseQuery') } }