Skip to content
Open
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: 6 additions & 0 deletions .changeset/noinfer-infinite-suspense-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/react-query': patch
'@tanstack/preact-query': patch
---

Add `NoInfer` to the return types of `useInfiniteQuery`, `useSuspenseQuery`, and `useSuspenseInfiniteQuery` so that an explicitly annotated result type can no longer reverse-infer `TData`, matching `useQuery`. A distributive wrapper is used so discriminated-union narrowing on `data` keeps working.
37 changes: 37 additions & 0 deletions packages/preact-query/src/__tests__/useInfiniteQuery.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { InfiniteData } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import { describe, expectTypeOf, it } from 'vitest'

import type { UseInfiniteQueryResult } from '../types'
import { useInfiniteQuery } from '../useInfiniteQuery'

describe('pageParam', () => {
Expand Down Expand Up @@ -142,3 +143,39 @@ describe('error booleans', () => {
expectTypeOf(isRefetchError).toEqualTypeOf<boolean>()
})
})

describe('NoInfer', () => {
// eslint-disable-next-line vitest/expect-expect
it('TData should depend only on the arguments, not the annotated result', () => {
// @ts-expect-error
const result: UseInfiniteQueryResult<InfiniteData<{ wow: string }>> =
useInfiniteQuery({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
initialPageParam: 1,
getNextPageParam: () => undefined,
})

void result
})

it('should preserve discriminated-union narrowing on data', () => {
type Item =
| { type: 'first'; first: string }
| { type: 'second'; second: string }

const { data } = useInfiniteQuery({
queryKey: queryKey(),
queryFn: (): Item => ({ type: 'first', first: 'a' }),
initialPageParam: 1,
getNextPageParam: () => undefined,
select: (infiniteData) => infiniteData.pages[0],
})

const second = data?.type === 'first' ? undefined : data

expectTypeOf(second).toEqualTypeOf<
{ type: 'second'; second: string } | undefined
Comment thread
coderabbitai[bot] marked this conversation as resolved.
>()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { InfiniteData } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import { assertType, describe, expectTypeOf, it } from 'vitest'

import type { UseSuspenseInfiniteQueryResult } from '../types'
import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery'

describe('useSuspenseInfiniteQuery', () => {
Expand Down Expand Up @@ -93,3 +94,39 @@ describe('useSuspenseInfiniteQuery', () => {
expectTypeOf(query).not.toHaveProperty('isPlaceholderData')
})
})

describe('NoInfer', () => {
// eslint-disable-next-line vitest/expect-expect
it('TData should depend only on the arguments, not the annotated result', () => {
// @ts-expect-error
const result: UseSuspenseInfiniteQueryResult<InfiniteData<{ wow: string }>> =
useSuspenseInfiniteQuery({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
initialPageParam: 1,
getNextPageParam: () => 1,
})

void result
})

it('should preserve discriminated-union narrowing on data', () => {
type Item =
| { type: 'first'; first: string }
| { type: 'second'; second: string }

const { data } = useSuspenseInfiniteQuery({
queryKey: queryKey(),
queryFn: (): Item => ({ type: 'first', first: 'a' }),
initialPageParam: 1,
getNextPageParam: () => 1,
select: (infiniteData) => infiniteData.pages[0],
})

const second = data?.type === 'first' ? undefined : data

expectTypeOf(second).toEqualTypeOf<
{ type: 'second'; second: string } | undefined
>()
})
})
31 changes: 31 additions & 0 deletions packages/preact-query/src/__tests__/useSuspenseQuery.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { skipToken } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import { assertType, describe, expectTypeOf, it } from 'vitest'

import type { UseSuspenseQueryResult } from '../types'
import { useSuspenseQuery } from '../useSuspenseQuery'

describe('useSuspenseQuery', () => {
Expand Down Expand Up @@ -88,3 +89,33 @@ describe('useSuspenseQuery', () => {
}
})
})

describe('NoInfer', () => {
// eslint-disable-next-line vitest/expect-expect
it('TData should depend only on the arguments, not the annotated result', () => {
// @ts-expect-error
const result: UseSuspenseQueryResult<{ wow: string }> = useSuspenseQuery({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
})

void result
})

it('should preserve discriminated-union narrowing on data', () => {
type Result =
| { type: 'first'; first: string }
| { type: 'second'; second: string }

const { data } = useSuspenseQuery({
queryKey: queryKey(),
queryFn: (): Result => ({ type: 'first', first: 'a' }),
})

const second = data.type === 'first' ? undefined : data

expectTypeOf(second).toEqualTypeOf<
{ type: 'second'; second: string } | undefined
>()
})
})
8 changes: 5 additions & 3 deletions packages/preact-query/src/useInfiniteQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
} from './types'
import { useBaseQuery } from './useBaseQuery'

type NarrowableNoInfer<T> = T extends unknown ? NoInfer<T> : never

export function useInfiniteQuery<
TQueryFnData,
TError = DefaultError,
Expand All @@ -33,7 +35,7 @@ export function useInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): DefinedUseInfiniteQueryResult<TData, TError>
): DefinedUseInfiniteQueryResult<NarrowableNoInfer<TData>, TError>

export function useInfiniteQuery<
TQueryFnData,
Expand All @@ -50,7 +52,7 @@ export function useInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>
): UseInfiniteQueryResult<NarrowableNoInfer<TData>, TError>

export function useInfiniteQuery<
TQueryFnData,
Expand All @@ -67,7 +69,7 @@ export function useInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>
): UseInfiniteQueryResult<NarrowableNoInfer<TData>, TError>

export function useInfiniteQuery(
options: UseInfiniteQueryOptions,
Expand Down
4 changes: 3 additions & 1 deletion packages/preact-query/src/useSuspenseInfiniteQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type {
} from './types'
import { useBaseQuery } from './useBaseQuery'

type NarrowableNoInfer<T> = T extends unknown ? NoInfer<T> : never

export function useSuspenseInfiniteQuery<
TQueryFnData,
TError = DefaultError,
Expand All @@ -30,7 +32,7 @@ export function useSuspenseInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): UseSuspenseInfiniteQueryResult<TData, TError> {
): UseSuspenseInfiniteQueryResult<NarrowableNoInfer<TData>, TError> {
if (process.env.NODE_ENV !== 'production') {
if ((options.queryFn as any) === skipToken) {
console.error('skipToken is not allowed for useSuspenseInfiniteQuery')
Expand Down
4 changes: 3 additions & 1 deletion packages/preact-query/src/useSuspenseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { defaultThrowOnError } from './suspense'
import type { UseSuspenseQueryOptions, UseSuspenseQueryResult } from './types'
import { useBaseQuery } from './useBaseQuery'

type NarrowableNoInfer<T> = T extends unknown ? NoInfer<T> : never

export function useSuspenseQuery<
TQueryFnData = unknown,
TError = DefaultError,
Expand All @@ -13,7 +15,7 @@ export function useSuspenseQuery<
>(
options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
queryClient?: QueryClient,
): UseSuspenseQueryResult<TData, TError> {
): UseSuspenseQueryResult<NarrowableNoInfer<TData>, TError> {
if (process.env.NODE_ENV !== 'production') {
if ((options.queryFn as any) === skipToken) {
console.error('skipToken is not allowed for useSuspenseQuery')
Expand Down
37 changes: 37 additions & 0 deletions packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { QueryClient } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import { useInfiniteQuery } from '../useInfiniteQuery'
import type { InfiniteData } from '@tanstack/query-core'
import type { UseInfiniteQueryResult } from '../types'

describe('pageParam', () => {
it('initialPageParam should define type of param passed to queryFunctionContext', () => {
Expand Down Expand Up @@ -141,3 +142,39 @@ describe('error booleans', () => {
expectTypeOf(isRefetchError).toEqualTypeOf<boolean>()
})
})

describe('NoInfer', () => {
// eslint-disable-next-line vitest/expect-expect
it('TData should depend only on the arguments, not the annotated result', () => {
// @ts-expect-error
const result: UseInfiniteQueryResult<InfiniteData<{ wow: string }>> =
useInfiniteQuery({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
initialPageParam: 1,
getNextPageParam: () => undefined,
})

void result
})

it('should preserve discriminated-union narrowing on data', () => {
type Item =
| { type: 'first'; first: string }
| { type: 'second'; second: string }

const { data } = useInfiniteQuery({
queryKey: queryKey(),
queryFn: (): Item => ({ type: 'first', first: 'a' }),
initialPageParam: 1,
getNextPageParam: () => undefined,
select: (infiniteData) => infiniteData.pages[0],
})

const second = data?.type === 'first' ? undefined : data

expectTypeOf(second).toEqualTypeOf<
{ type: 'second'; second: string } | undefined
>()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { skipToken } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery'
import type { InfiniteData } from '@tanstack/query-core'
import type { UseSuspenseInfiniteQueryResult } from '../types'

describe('useSuspenseInfiniteQuery', () => {
it('should always have data defined', () => {
Expand Down Expand Up @@ -92,3 +93,39 @@ describe('useSuspenseInfiniteQuery', () => {
expectTypeOf(query).not.toHaveProperty('isPlaceholderData')
})
})

describe('NoInfer', () => {
// eslint-disable-next-line vitest/expect-expect
it('TData should depend only on the arguments, not the annotated result', () => {
// @ts-expect-error
const result: UseSuspenseInfiniteQueryResult<InfiniteData<{ wow: string }>> =
useSuspenseInfiniteQuery({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
initialPageParam: 1,
getNextPageParam: () => 1,
})

void result
})

it('should preserve discriminated-union narrowing on data', () => {
type Item =
| { type: 'first'; first: string }
| { type: 'second'; second: string }

const { data } = useSuspenseInfiniteQuery({
queryKey: queryKey(),
queryFn: (): Item => ({ type: 'first', first: 'a' }),
initialPageParam: 1,
getNextPageParam: () => 1,
select: (infiniteData) => infiniteData.pages[0],
})

const second = data?.type === 'first' ? undefined : data

expectTypeOf(second).toEqualTypeOf<
{ type: 'second'; second: string } | undefined
>()
})
})
31 changes: 31 additions & 0 deletions packages/react-query/src/__tests__/useSuspenseQuery.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assertType, describe, expectTypeOf, it } from 'vitest'
import { skipToken } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import { useSuspenseQuery } from '../useSuspenseQuery'
import type { UseSuspenseQueryResult } from '../types'

describe('useSuspenseQuery', () => {
it('should always have data defined', () => {
Expand Down Expand Up @@ -87,3 +88,33 @@ describe('useSuspenseQuery', () => {
}
})
})

describe('NoInfer', () => {
// eslint-disable-next-line vitest/expect-expect
it('TData should depend only on the arguments, not the annotated result', () => {
// @ts-expect-error
const result: UseSuspenseQueryResult<{ wow: string }> = useSuspenseQuery({
queryKey: queryKey(),
queryFn: () => ({ wow: true }),
})

void result
})

it('should preserve discriminated-union narrowing on data', () => {
type Result =
| { type: 'first'; first: string }
| { type: 'second'; second: string }

const { data } = useSuspenseQuery({
queryKey: queryKey(),
queryFn: (): Result => ({ type: 'first', first: 'a' }),
})

const second = data.type === 'first' ? undefined : data

expectTypeOf(second).toEqualTypeOf<
{ type: 'second'; second: string } | undefined
>()
})
})
8 changes: 5 additions & 3 deletions packages/react-query/src/useInfiniteQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
UndefinedInitialDataInfiniteOptions,
} from './infiniteQueryOptions'

type NarrowableNoInfer<T> = T extends unknown ? NoInfer<T> : never

export function useInfiniteQuery<
TQueryFnData,
TError = DefaultError,
Expand All @@ -33,7 +35,7 @@ export function useInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): DefinedUseInfiniteQueryResult<TData, TError>
): DefinedUseInfiniteQueryResult<NarrowableNoInfer<TData>, TError>

export function useInfiniteQuery<
TQueryFnData,
Expand All @@ -50,7 +52,7 @@ export function useInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>
): UseInfiniteQueryResult<NarrowableNoInfer<TData>, TError>

export function useInfiniteQuery<
TQueryFnData,
Expand All @@ -67,7 +69,7 @@ export function useInfiniteQuery<
TPageParam
>,
queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>
): UseInfiniteQueryResult<NarrowableNoInfer<TData>, TError>

export function useInfiniteQuery(
options: UseInfiniteQueryOptions,
Expand Down
Loading