-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(types): enable types in useQueries #2634
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/tanstack/react-query/5ydQs6QhdipMm4aZefQPyaFnL2Fr |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 4ec115c:
|
Thank you for working on this 🙏 prettier formatting seems to be off, which is why the pipeline fails, please have a look. Also, I played around with this a bit, here are my findings:
result type should be an Observer of
here,
this errors with:
|
Thanks for the feedback; I'm gunna have to think about the solution a bit more as it sounds like I had only covered the set of Experimenting with a second type parameter of equal length but I may need to do some work to pass the types through the function useQueries<
TQueriesFnData extends unknown[],
TQueriesData extends {[E in keyof TQueriesFnData] : TQueriesData[E]},
>(
queries: [...QueriesOptions<TQueriesFnData, TQueriesData>] // TQueriesData analogous to TData
) Will keep you posted; it's an interesting puzzle 😄 |
Hi again, Sorry took a while to explore. I think I've come up with something that's workable. Originally I had hoped I could do it with a single mapped type, however, because it needs to track multiple types that can be different for each element, I've ended up having to write a bunch of helpers + recursion to cycle through the array 1 entry at a time (there's a lot of these nested "X extends infer Y?" statements unfortunately) Anyways, the good news:
Let me know what you think. There's a bit of complexity, but I've tried to add tests to cover all the possibilities; it should be pretty stable unless the underlying types change significantly? (in which case we'd need to update the utility types here as well). Covering last week's comments specifically:
Think this should be working now
Updated it to discriminate between
I'm afraid I've hit a wall on this useQueries([
{
queryKey: "key1",
queryFn: () => 1,
initialData: 1,
select: data1 => String(data1) // TS will complain about noImplicitAny
},
{
queryKey: "key2",
queryFn: () => 1,
initialData: 1,
select: (data1: number) => String(data1) // TS will allow this, and my IDE even suggests the number type on hover, but I need to add it explicitly to avoid the noImplicitAny
},
{
queryKey: "key3",
queryFn: () => "1",
initialData: "1",
select: (data2: number) => data2 // TS will complain as it should
},
]);
I believe Array.map / similar array methods all return arrays rather than a tuples, so this process can't handle them like array literals. However I've updated it so that if it can't infer a tuple, it will revert to a regular unknown |
src/react/useQueries.ts
Outdated
// Map params from object {queryFnData: TQueryFnData, error: TError, data: TData} | ||
T extends { | ||
queryFnData: infer TQueryFnData | ||
error?: infer TError | ||
data: infer TData | ||
} | ||
? UseQueryOptions<TQueryFnData, TError, TData> | ||
: T extends { queryFnData: infer TQueryFnData; error?: infer TError } | ||
? UseQueryOptions<TQueryFnData, TError> | ||
: T extends { data: infer TData; error?: infer TError } | ||
? UseQueryOptions<unknown, TError, TData> | ||
: // Map params from tuple [TQueryFnData, TError, TData] | ||
T extends [infer TQueryFnData, infer TError, infer TData] | ||
? UseQueryOptions<TQueryFnData, TError, TData> | ||
: T extends [infer TQueryFnData, infer TError] | ||
? UseQueryOptions<TQueryFnData, TError> | ||
: T extends [infer TQueryFnData] | ||
? UseQueryOptions<TQueryFnData> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this + the same section of GetResults
can be slashed in half if we keep one of the two type parameter structures
src/react/useQueries.ts
Outdated
type QueriesOptions< | ||
T extends any[], | ||
Result extends any[] = [], | ||
Depth extends ReadonlyArray<number> = [] | ||
> = Depth['length'] extends MAXIMUM_DEPTH | ||
? UseQueryOptions[] | ||
: T extends [] | ||
? [] | ||
: T extends [infer Head] | ||
? [...Result, GetOptions<Head>] | ||
: T extends [infer Head, ...infer Tail] | ||
? QueriesOptions<[...Tail], [...Result, GetOptions<Head>], [...Depth, 1]> | ||
: T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to add a bit more context, why I ended up going down this recursive-array-map route...
Originally I had hoped I could do something a bit more elegant with two array type parameters TQueryFnData[]
and TData[]
mapped by a single operation, but ran into a brick wall as I can't index them with a single key. (or if I try and force it, it breaks the tuple magic)
Idea 1:
// ERROR: "E cannot be used to index TData"
type QueriesOptions<TQueryFnData, TData> = { [E in keyof TQueryFnData]: InferredQueryOptions<TQueryFnData[E], TData[E]> }
useQueries<TQueryFnData extends unknown[], TData extends unknown[]>(queries: [...QueriesOptions<TQueryFnData, TData>])
Idea 2
// something like this can be made to work, but QueriesOptions is no longer an array-type
type QueriesOptions<TQueryFnData, TData> = keyof TData extends keyof TQueryFnData ? { [E in keyof TQueryFnData]: InferredQueryOptions<TQueryFnData[E], TData[E]> } : never
// ERROR: "a rest parameter must be of an array type"
useQueries<TQueryFnData extends unknown[], TData extends unknown[]>(queries: [...QueriesOptions<TQueryFnData, TData>])
I think until TS introduce existential types and/or loosen their rules around the spread operator, I'm dependent on the "brute-force" recursive approach
Clicking the re-request review button just in case there was no notification sent out with the last updates I pushed (should have done this sooner but was travelling last couple of weeks). Any feedback let me know. |
sorry @artysidorenko, it really slipped past me and I didn't get any notifications about the new commits :( I'll try to have a look this week |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for working on this, and sorry again that I missed your commits.
- ✅ const assertions now work
- ✅ select now works
- 🟡 it's a bit unfortunate that
data
passed toselect
is implicitly any, e.g. here:
[
{
queryKey: "key2",
queryFn: () => 1,
select: (data) => String(data)
}
]
but I guess this isn't an easy fix?
- ✅ success and error look good, even though we have to type it explicitly. I guess same restriction as with select?
- 🔴 as commented on the test,
useQueries
with anarray.map
infers to<unknown, unknown>
. I think getting inference here would be crucial
Hi @TkDodo , thanks for looking at it. Pushed an update that covers Array.map, and cleans up the other test comment (also added a few more tests and tried to clarify the code comments a bit)
I'm afraid I'm stuck on this one indeed - I think it's related to how TS evaluates the contextual vs context-sensitive types in a sequential manner. If it's dealing with an array of context-sensitive types I reckon the compiler must be doing multiple rounds of evaluation which is preventing it from inferring the param types upfront. The best I could get was "enforce" rather than "infer" I guess it would mean if this gets released it might trigger some type errors in users' code, where people were using callbacks without typing the params. |
I think this looks really good. Complex (I don't fully understand everything tbh 😅) but really good, and very well tested 👍 quick last question: is there a minimum typescript version that we'd need to get this going? Is there any new language feature that you're using that will make it impossible for older versions of TypeScript (like 3.x) to work with it? The docs are currently saying:
and if that changes, we'd need to at least update the docs. |
Don't worry about it, I don't think it's required to get this PR shipped. I know we have similar issues when using |
Ah indeed, it might be another trade-off as all this requires TS 4.1+ (for its recursive conditional types, used to cycle through the tuple 1 element at a time). With older versions - including 4.0 - it will fail and give you I had a think if there's anything we could do... if some users really can't upgrade to 4.1 but also really need function useQueries<_ extends unknown, T extends any[] = []>(queries: readonly UseQueryOptions[]): UseQueryResult[];
But it might just be confusing/need documentation etc |
I think that overload is a good compromise. But please also amend the TypeScript docs that currently say you need 3.8, to something that says that 4.1 is needed if you want proper typings for useQueries. The important part is that users with 3.8 will not get any type errors in their code if they are using useQueries at the moment, and especially if they are only using useQuery :) |
Aha, so I did a bit more research (I wasn't very happy with the compromise I suggested after trying it out - it was looking quite fiddly trying to get the overload right, and there is enough complexity in there as it is 😅)... I found that we can get full backwards-compatibility after all: Added a directive in Let me know if it works for you. Added a snippet to the docs as well (let me know if it's worth adding something extra in the community section as well, to cover a bit more how to add the explicit type parameters?) |
import { useQueries } from './useQueries' | ||
export { useQueries } | ||
|
||
export * from '..' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
named export will always take priority over the wildcard export
"typesVersions": { | ||
"<4.1": { "types/*": ["types/ts3.8/*"] } | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know much about this feature tbh, but did a bunch of testing and it seems to work really well.
🎉 This PR is included in version 3.28.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
@artysidorenko I just wanted to take a moment to say thank you. The use of |
Hi folks, thanks for the great library, have been using it a lot recently!
I've had a stab at issue #1675 , taking inspiration from PR #1527 but with a slightly different approach:
useQueries
gets a type parameter in the shape of some array (representing the data types returned from thequeryFn
). This is inferred from the passed-inqueries
array.It also required tweaking some of the options property types slightly - namely
initialData
andplaceholderData
, to enforce the type that was set on thequeryFn
. Adding some comments to the PR diff to explain further.I think it should be backwards-compatible.
Let me know what you think.