Skip to content

Commit d423168

Browse files
ousamabenyounesclaudeTkDodo
authored
fix(query-core): use built-in NoInfer for generic indexed-access types (#10593)
* fix(query-core): use built-in NoInfer to support generic indexed-access types The local `NoInfer<T> = [T][T extends any ? 0 : never]` trick breaks down when `T` is a generic indexed-access type such as `DataTypeToEntity[DT]`, producing errors like "Type '[DataTypeToEntity[DT]]' is not assignable to type 'DataTypeToEntity'". Since the project already requires TypeScript >= 5.4, delegate the public `NoInfer<T>` alias to TypeScript's built-in `NoInfer` (introduced in 5.4). A small `noInfer.ts` helper exposes it as `IntrinsicNoInfer<T>` to avoid the local-shadow recursion problem. Vue's `setQueryData` implementation now forwards the `<TData>` generic to `super.setQueryData<TData>(...)` so its declared `NoInfer<TData> | undefined` return type stays satisfied under the stricter built-in `NoInfer`. Fixes #9937 * test(react-query): move 'generic indexed access TData' describe out of 'initialData' Block was nested inside `describe('initialData', ...)` but is unrelated to initialData behavior. Lift it to sibling level under `describe('useQuery', ...)` so test output groups it correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(react-query): document implicit regression guard for generic indexed-access NoInfer Per CodeRabbit nitpick: clarify that the `getLabel(props.dataType, data)` call itself is the regression guard. With the previous hand-rolled NoInfer, this call failed to type-check; with built-in NoInfer (TS 5.4+), it compiles. An explicit `expectTypeOf(data).toEqualTypeOf<...>()` / `toMatchTypeOf<...>()` cannot work here — vitest's type-relation checks reduce to `Extends<NoInfer<T>, T>` which TS leaves unresolved under a generic `TDataType extends DataType` constraint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(query-core): drop NoInfer re-export, use built-in everywhere Per @TkDodo's review: TypeScript's intrinsic NoInfer (TS ≥ 5.4) is the canonical type, and the package already requires TS ≥ 5.4. Removing the re-export sidesteps the self-shadowing issue entirely and avoids encouraging consumers to import a duplicate. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Dominik Dorfmeister 🔮 <office@dorfmeister.cc>
1 parent 5ff4f69 commit d423168

7 files changed

Lines changed: 57 additions & 17 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@tanstack/query-core': patch
3+
'@tanstack/vue-query': patch
4+
---
5+
6+
fix(query-core): drop the custom `NoInfer<T>` re-export and rely on TypeScript's built-in `NoInfer` (TS ≥ 5.4) so `NoInfer<X[K]>` stays assignable to `X[K]` in generic contexts (fixes #9937)

packages/preact-query/src/useQuery.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { QueryObserver } from '@tanstack/query-core'
2-
import type {
3-
DefaultError,
4-
NoInfer,
5-
QueryClient,
6-
QueryKey,
7-
} from '@tanstack/query-core'
2+
import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core'
83

94
import type {
105
DefinedInitialDataOptions,

packages/query-core/src/queryClient.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import type {
2929
MutationKey,
3030
MutationObserverOptions,
3131
MutationOptions,
32-
NoInfer,
3332
OmitKeyof,
3433
QueryClientConfig,
3534
QueryKey,

packages/query-core/src/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ export type Override<TTargetA, TTargetB> = {
3434
: TTargetA[AKey]
3535
}
3636

37-
export type NoInfer<T> = [T][T extends any ? 0 : never]
38-
3937
export interface Register {
4038
// defaultError: Error
4139
// queryMeta: Record<string, unknown>

packages/react-query/src/__tests__/useQuery.test-d.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,52 @@ describe('useQuery', () => {
338338
})
339339
})
340340
})
341+
342+
describe('generic indexed access TData', () => {
343+
// https://github.com/TanStack/query/issues/9937
344+
it('should be assignable back to its source indexed type when passed to a generic function parameter', () => {
345+
enum DataType {
346+
Account = 'account',
347+
Product = 'product',
348+
}
349+
350+
interface Account {
351+
name: string
352+
}
353+
interface Product {
354+
code: string
355+
}
356+
357+
type DataTypeToEntity = {
358+
[DataType.Account]: Account
359+
[DataType.Product]: Product
360+
}
361+
362+
const getData = <TDataType extends DataType>(
363+
_dataType: TDataType,
364+
): Promise<DataTypeToEntity[TDataType]> =>
365+
Promise.resolve({} as DataTypeToEntity[TDataType])
366+
367+
const getLabel = <TDataType extends DataType>(
368+
_dataType: TDataType,
369+
_data: DataTypeToEntity[TDataType],
370+
) => 'test'
371+
372+
function Test<TDataType extends DataType>(props: {
373+
dataType: TDataType
374+
}) {
375+
const { data } = useQuery({
376+
queryKey: ['test'],
377+
queryFn: () => getData(props.dataType),
378+
})
379+
380+
// Regression guard: this call must compile. With the previous
381+
// hand-rolled NoInfer, `data` failed to flow back into the generic
382+
// indexed-access parameter `DataTypeToEntity[TDataType]`.
383+
return data ? getLabel(props.dataType, data) : null
384+
}
385+
386+
expectTypeOf(Test).toBeFunction()
387+
})
388+
})
341389
})

packages/react-query/src/useQuery.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
'use client'
22
import { QueryObserver } from '@tanstack/query-core'
33
import { useBaseQuery } from './useBaseQuery'
4-
import type {
5-
DefaultError,
6-
NoInfer,
7-
QueryClient,
8-
QueryKey,
9-
} from '@tanstack/query-core'
4+
import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core'
105
import type {
116
DefinedUseQueryResult,
127
UseQueryOptions,

packages/vue-query/src/queryClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import type {
2121
MutationFilters,
2222
MutationKey,
2323
MutationObserverOptions,
24-
NoInfer,
2524
OmitKeyof,
2625
QueryFilters,
2726
QueryKey,
@@ -125,7 +124,7 @@ export class QueryClient extends QC {
125124
updater: Updater<TData | undefined, TData | undefined>,
126125
options: MaybeRefDeep<SetDataOptions> = {},
127126
): NoInfer<TData> | undefined {
128-
return super.setQueryData(
127+
return super.setQueryData<TData>(
129128
cloneDeepUnref(queryKey),
130129
updater,
131130
cloneDeepUnref(options),

0 commit comments

Comments
 (0)