Generics in React Query
React Query author Tanner Linsley explains how generics flow through functions in an app that uses the library.
Instead of using generic names like T, U, V, Tanner suggests being more descriptive.
He emphasizes the importance of passing generics in the right slots and hovering over the options object to see their signature and order.
0:00 I chatted to Tanner Linsley, the author of React Query about generic inference. Here's what he had to say.
0:05 Something that took me a while to realize with TypeScript is that generics flow through your app in all the different functions, just based on how you pass generics around.
0:17 You're going to see TData and TQueryFnData and TQueryData. You're going to see that everywhere. Something to understand is that we didn't register this TQueryFnData generic somewhere global to our system.
0:32 This is just a name that we chose. I will fight anybody who says, "You need to name your generics as just T or U or V or whatever." Give them names, like that's ridiculous, but we just consistently named everything.
0:45 You have to make sure that you're passing those generics in the right slots. You'll see with this QueryObserverFunction or Options, like all the options objects, you have to pass TQueryFnData as the first one to those options objects.
1:01 If you hover over one of those options objects, it will show you the signature and what it expects the order of those generics. These generics just represent some type that your users have given you. Either that they've supplied directly or that something a function they've given you is returning or whatever.
1:22 The weird thing about generics is they feel it like you can specify them from the top down of your architecture, but they can fill in from the bottom up. That's something that took a really long time to understand, when I was learning about generics.
1:41 You can see that we're not really inferring anything in these higher types, these higher functions. We're just shuttling generics down into things that we extend or options objects or whatever.
1:56 Let's dive into QueryOptions here. See, now I have QueryOptions and this one's not shuttling a ton, but try and find this TQueryFnData in here. We're passing this generic down to a QueryFunction type, and this is the QueryFunction that users are passing in.
2:15 Let's open that up. It says, "This is a function that gives you the context that has a QueryKey and then returns either a promise or directly the TQueryFnData." All the way down here in this QueryFunction type, users pass in their QueryFunction that returns a list of blog posts. That type, that list of blog posts now is going to work its way up.
2:45 Let's pause the interview there briefly to look at the useQuery hook and explain exactly how these generics flow through. We've got this useQuery hook, which is how you consume React Query most of the time. We've got TQueryFnData, which is what Tanner was talking about.
3:00 Inside here, we have options, which is useQueryOptions, where we're passing TQueryFnData. Let's command click, look at that. Here we have useQueryOptions. TQueryFnData is a kind of empty interface here, which is extending UseBaseQueryOptions. Again, we're sticking everything in here too.
3:21 UseBaseQueryOptions, this extends ContextOptions, QueryObserverOptions. Let's take a look at ContextOptions. Oh yeah, it's a little bit of stuff here. How about QueryObserverOptions? QueryObserverOptions, we're now inside packages/query-core as well.
3:37 TQueryFnData extends QueryOptions and now we're back where we were. Inside QueryOptions with TQueryFnData and the QueryFunction gets put inside here.
3:48 This means that when we try this out and when we call useQuery, let's say, const = useQuery. Now, when we pass in queryFn, let's say, we're returning an array of numbers, one, two, three. This is going to get locked in inside there and we have that inside our TQueryFnData.
4:09 All of those generics just feed in all the way around the entire library and they build out all of this inference. When Tanner was saying, this gets inferred from the bottom, up all the way through here, if we just roll through that again.
4:23 UseQueryOptions, UseBaseQueryOptions, to QueryObserverOptions, to QueryOptions, to QueryFunction, QueryFunction, finally there, we have a function that returns either T or Promise T.
4:38 That fills in from the bottom up and then the rest of the function or the rest of the types within the scope of the function, if that makes sense, can then use the inferred type.