Skip to content

Commit

Permalink
fix(types): prevent type errors and improve inference for dynamic que…
Browse files Browse the repository at this point in the history
…ries on useQueries and useSuspenseQueries (#8624)

* fix(react-query): prevent type errors and improve inference for dynamic queries on useQueries and useSuspenseQueries

Previously, using useQueries and useSuspenseQueries with a dynamic array of mixed queries caused type errors. This commit ensures that type errors no longer occur and that the returned data is correctly inferred (e.g., as (number | boolean | undefined)[]) instead of unknown[].

* fix(react-query): improve type inference for useQueries and useSuspenseQueries results

* fix(react-query): fix type of queries in useQueries and useSuspenseQueries

* refactor: remove meaningless depths

* test(react-query): move type only tests

* test(react-query): fix type test error

* fix(vue-query): prevent type errors and improve inference for dynamicqueries on useQueries

* fix:  prevent type errors and improve inference for dynamicqueries

* fix: solid type tests

not sure why, but compilation must run first with the current tsc version in case solid-query isn't built yet

* fix: prevent type errors and improve inference for dynamic queries

* chore: remove test

* test(react-query): fix failing type tests

* fix: fix test syntax error

* test(react-query): remove tests for result[0].data

* fix: change the result fallback to fix test fail

* rollback(angular-query-experimental): restore injectQueries

---------

Co-authored-by: Dominik Dorfmeister <[email protected]>
  • Loading branch information
gs18004 and TkDodo authored Feb 21, 2025
1 parent a4db9ed commit f63ba16
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 94 deletions.
4 changes: 4 additions & 0 deletions packages/angular-query-experimental/src/inject-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ export type QueriesResults<
Array<QueryObserverResult>

/**
* @param root0
* @param root0.queries
* @param root0.combine
* @param injector
* @public
*/
export function injectQueries<
Expand Down
26 changes: 26 additions & 0 deletions packages/react-query/src/__tests__/useQueries.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,30 @@ describe('UseQueries config object overload', () => {
expectTypeOf(firstResult).toEqualTypeOf<UseQueryResult<number, Error>>()
expectTypeOf(firstResult.data).toEqualTypeOf<number | undefined>()
})

it('should return correct data for dynamic queries with mixed result types', () => {
const Queries1 = {
get: () =>
queryOptions({
queryKey: ['key1'],
queryFn: () => Promise.resolve(1),
}),
}
const Queries2 = {
get: () =>
queryOptions({
queryKey: ['key2'],
queryFn: () => Promise.resolve(true),
}),
}

const queries1List = [1, 2, 3].map(() => ({ ...Queries1.get() }))
const result = useQueries({
queries: [...queries1List, { ...Queries2.get() }],
})

expectTypeOf(result).toEqualTypeOf<
[...Array<UseQueryResult<number, Error>>, UseQueryResult<boolean, Error>]
>()
})
})
29 changes: 29 additions & 0 deletions packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,35 @@ describe('UseSuspenseQueries config object overload', () => {
})
})

it('should return correct data for dynamic queries with mixed result types', () => {
const Queries1 = {
get: () =>
queryOptions({
queryKey: ['key1'],
queryFn: () => Promise.resolve(1),
}),
}
const Queries2 = {
get: () =>
queryOptions({
queryKey: ['key2'],
queryFn: () => Promise.resolve(true),
}),
}

const queries1List = [1, 2, 3].map(() => ({ ...Queries1.get() }))
const result = useSuspenseQueries({
queries: [...queries1List, { ...Queries2.get() }],
})

expectTypeOf(result).toEqualTypeOf<
[
...Array<UseSuspenseQueryResult<number, Error>>,
UseSuspenseQueryResult<boolean, Error>,
]
>()
})

it('queryOptions with initialData works on useSuspenseQueries', () => {
const query1 = queryOptions({
queryKey: ['key1'],
Expand Down
22 changes: 4 additions & 18 deletions packages/react-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,23 +203,7 @@ export type QueriesResults<
[...TResults, GetUseQueryResult<Head>],
[...TDepth, 1]
>
: T extends Array<
UseQueryOptionsForUseQueries<
infer TQueryFnData,
infer TError,
infer TData,
any
>
>
? // Dynamic-size (homogenous) UseQueryOptions array: map directly to array of results
Array<
UseQueryResult<
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
>
: // Fallback
Array<UseQueryResult>
: { [K in keyof T]: GetUseQueryResult<T[K]> }

export function useQueries<
T extends Array<any>,
Expand All @@ -229,7 +213,9 @@ export function useQueries<
queries,
...options
}: {
queries: readonly [...QueriesOptions<T>]
queries:
| readonly [...QueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetUseQueryOptionsForUseQueries<T[K]> }]
combine?: (result: QueriesResults<T>) => TCombinedResult
subscribed?: boolean
},
Expand Down
22 changes: 4 additions & 18 deletions packages/react-query/src/useSuspenseQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,30 +160,16 @@ export type SuspenseQueriesResults<
[...TResults, GetUseSuspenseQueryResult<Head>],
[...TDepth, 1]
>
: T extends Array<
UseSuspenseQueryOptions<
infer TQueryFnData,
infer TError,
infer TData,
any
>
>
? // Dynamic-size (homogenous) UseQueryOptions array: map directly to array of results
Array<
UseSuspenseQueryResult<
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
>
: // Fallback
Array<UseSuspenseQueryResult>
: { [K in keyof T]: GetUseSuspenseQueryResult<T[K]> }

export function useSuspenseQueries<
T extends Array<any>,
TCombinedResult = SuspenseQueriesResults<T>,
>(
options: {
queries: readonly [...SuspenseQueriesOptions<T>]
queries:
| readonly [...SuspenseQueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetUseSuspenseQueryOptions<T[K]> }]
combine?: (result: SuspenseQueriesResults<T>) => TCombinedResult
},
queryClient?: QueryClient,
Expand Down
3 changes: 1 addition & 2 deletions packages/solid-query-devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
"clean": "premove ./build ./coverage ./dist-ts",
"compile": "tsc --build",
"test:eslint": "eslint ./src",
"test:types": "npm-run-all --serial test:types:*",
"test:types": "npm run compile && npm-run-all --serial test:types:*",
"test:types:ts50": "node ../../node_modules/typescript50/lib/tsc.js --build",
"test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js --build",
"test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js --build",
"test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build",
"test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build",
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
"test:types:ts57": "tsc --build",
"test:build": "publint --strict && attw --pack",
"build": "tsup --tsconfig tsconfig.prod.json",
"build:dev": "tsup --watch"
Expand Down
3 changes: 1 addition & 2 deletions packages/solid-query-persist-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
"clean": "premove ./build ./coverage ./dist-ts",
"compile": "tsc --build",
"test:eslint": "eslint ./src",
"test:types": "npm-run-all --serial test:types:*",
"test:types": "npm run compile && npm-run-all --serial test:types:*",
"test:types:ts50": "node ../../node_modules/typescript50/lib/tsc.js --build",
"test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js --build",
"test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js --build",
"test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build",
"test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build",
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
"test:types:ts57": "tsc --build",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
"test:build": "publint --strict && attw --pack",
Expand Down
34 changes: 34 additions & 0 deletions packages/solid-query/src/__tests__/createQueries.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expectTypeOf, it } from 'vitest'
import { createQueries, queryOptions } from '..'
import type { CreateQueryResult } from '..'

describe('createQueries', () => {
it('should return correct data for dynamic queries with mixed result types', () => {
const Queries1 = {
get: () =>
queryOptions({
queryKey: ['key1'],
queryFn: () => Promise.resolve(1),
}),
}
const Queries2 = {
get: () =>
queryOptions({
queryKey: ['key2'],
queryFn: () => Promise.resolve(true),
}),
}

const queries1List = [1, 2, 3].map(() => ({ ...Queries1.get() }))
const result = createQueries(() => ({
queries: [...queries1List, { ...Queries2.get() }],
}))

expectTypeOf(result).toEqualTypeOf<
[
...Array<CreateQueryResult<number, Error>>,
CreateQueryResult<boolean, Error>,
]
>()
})
})
22 changes: 4 additions & 18 deletions packages/solid-query/src/createQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,30 +183,16 @@ type QueriesResults<
[...TResult, GetResults<Head>],
[...TDepth, 1]
>
: T extends Array<
CreateQueryOptionsForCreateQueries<
infer TQueryFnData,
infer TError,
infer TData,
any
>
>
? // Dynamic-size (homogenous) UseQueryOptions array: map directly to array of results
Array<
CreateQueryResult<
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
>
: // Fallback
Array<CreateQueryResult>
: { [K in keyof T]: GetResults<T[K]> }

export function createQueries<
T extends Array<any>,
TCombinedResult extends QueriesResults<T> = QueriesResults<T>,
>(
queriesOptions: Accessor<{
queries: readonly [...QueriesOptions<T>]
queries:
| readonly [...QueriesOptions<T>]
| readonly [...{ [K in keyof T]: GetOptions<T[K]> }]
combine?: (result: QueriesResults<T>) => TCombinedResult
}>,
queryClient?: Accessor<QueryClient>,
Expand Down
24 changes: 6 additions & 18 deletions packages/svelte-query/src/createQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,23 +184,7 @@ export type QueriesResults<
[...TResults, GetCreateQueryResult<Head>],
[...TDepth, 1]
>
: T extends Array<
QueryObserverOptionsForCreateQueries<
infer TQueryFnData,
infer TError,
infer TData,
any
>
>
? // Dynamic-size (homogenous) CreateQueryOptions array: map directly to array of results
Array<
QueryObserverResult<
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
>
: // Fallback
Array<QueryObserverResult>
: { [K in keyof T]: GetCreateQueryResult<T[K]> }

export function createQueries<
T extends Array<any>,
Expand All @@ -210,7 +194,11 @@ export function createQueries<
queries,
...options
}: {
queries: StoreOrVal<[...QueriesOptions<T>]>
queries:
| StoreOrVal<[...QueriesOptions<T>]>
| StoreOrVal<
[...{ [K in keyof T]: GetQueryObserverOptionsForCreateQueries<T[K]> }]
>
combine?: (result: QueriesResults<T>) => TCombinedResult
},
queryClient?: QueryClient,
Expand Down
36 changes: 36 additions & 0 deletions packages/svelte-query/tests/createQueries/createQueries.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expectTypeOf, test } from 'vitest'
import { get } from 'svelte/store'
import { skipToken } from '@tanstack/query-core'
import { createQueries, queryOptions } from '../../src/index.js'
import type { Readable } from 'svelte/store'
import type { OmitKeyof, QueryObserverResult } from '@tanstack/query-core'
import type { CreateQueryOptions } from '../../src/index.js'

Expand Down Expand Up @@ -65,4 +66,39 @@ describe('createQueries', () => {
>()
expectTypeOf(firstResult.data).toEqualTypeOf<number | undefined>()
})

test('should return correct data for dynamic queries with mixed result types', () => {
const Queries1 = {
get: () =>
queryOptions({
queryKey: ['key1'],
queryFn: () => Promise.resolve(1),
}),
}
const Queries2 = {
get: () =>
queryOptions({
queryKey: ['key2'],
queryFn: () => Promise.resolve(true),
}),
}

const queries1List = [1, 2, 3].map(() => ({ ...Queries1.get() }))
const result = createQueries({
queries: [...queries1List, { ...Queries2.get() }],
})

expectTypeOf(result).toEqualTypeOf<
Readable<
[
...Array<QueryObserverResult<number, Error>>,
QueryObserverResult<boolean, Error>,
]
>
>()

expectTypeOf(get(result)[0].data).toEqualTypeOf<
number | boolean | undefined
>()
})
})
33 changes: 33 additions & 0 deletions packages/vue-query/src/__tests__/useQueries.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,37 @@ describe('UseQueries config object overload', () => {

expectTypeOf(queryCombineWithoutSelect.value).toEqualTypeOf<Array<number>>()
})

it('should return correct data for dynamic queries with mixed result types', () => {
const Queries1 = {
get: () =>
queryOptions({
queryKey: ['key1'],
queryFn: () => Promise.resolve(1),
}),
}
const Queries2 = {
get: () =>
queryOptions({
queryKey: ['key2'],
queryFn: () => Promise.resolve(true),
}),
}

const queries1List = [1, 2, 3].map(() => ({ ...Queries1.get() }))
const { value: queriesState } = useQueries({
queries: [...queries1List, { ...Queries2.get() }],
})

expectTypeOf(queriesState).toEqualTypeOf<
[
...Array<QueryObserverResult<number, Error>>,
QueryObserverResult<boolean, Error>,
]
>()

expectTypeOf(queriesState[0].data).toEqualTypeOf<
number | boolean | undefined
>()
})
})
Loading

0 comments on commit f63ba16

Please sign in to comment.