Working with External Libraries 5 exercises
solution

Handling Type Arguments in a Custom Query Hook

Here's our starting point:


const useApi = (
queryKey: any[],
queryFn: (key: any, token: string) => Promise<any>,
) => {
const token = useAuthToken();
return useQuery({
queryFn: (ctx) => queryFn(ctx, token),
queryKey,
});
};

The first thing we need to do is dete

Loading solution

Transcript

00:00 Okay, let's give this a go. Now, the question here is how many type arguments do we want to use here? This isn't exactly the same as React Select where we wanted to exactly copy the API. It's got a very, very small API, really, and we just need to make sure that useQuery, the data here, is being inferred correctly.

00:18 So if we take a look at useQuery, these horrible overloads again, we've got tQuery function data, tError, tData, and tQueryKey. There's some useful stuff that we can look at here immediately. We have tQuery function data, which could be anything, and so it's typed as equals unknown.

00:36 And we've got this tQueryKey here, which appears to represent the key that you pass in. So we know that query key is definitely something we need to infer, and probably the query function data is going to be something we need to grab too. So that makes a good argument, therefore, including those as our type arguments.

00:54 We don't need to replicate the same order that they're in or anything like that. In fact, it's probably better if we do them in the order that they appear in our runtime arguments. So let's say we have tQueryKey and then tQuery function data.

01:12 That's going to give us a nice little sort of setup there. Now, query function, it takes in a key and then it takes in a token and returns promise any. And let's take a look here. So this promise any, that's probably going to be returning our tQuery function data there.

01:31 Now, that's nice because it means that this use query then, we're calling our query function and that tQuery function data is going to be put into use query. So that's good already. Now, tQueryKey, where's that going to be inferred? Well, it's going to be put here. Now, query key, we can put that as tQueryKey.

01:49 Now, immediately, we have some issues here because query key, it's supposed to be typed in a certain way. And you can see our inference actually get ruined immediately. Unknown, unknown, unknown query key. That's terrifying. And really, we're expecting this sort of thing that we pass in here to be constrained to a certain level.

02:09 And we know that we want it to be probably an array of strings or an array of numbers. But we also know that use query has got a type that can help us out. We've got up here, where is it? Query key and query key is probably going to be the type that we need to use. So let's grab query key from here and let's say that extends query key.

02:30 OK, now things are looking a little bit tidier. Now we're getting our inference back. When we didn't constrain this, it looked like it was hitting some sort of strange overload that wasn't really working here. Or at least it wasn't making sense of the overload that we're trying to call. And so we got these strange errors. Query function does not exist on that type.

02:48 But now that we're passing query key in there, it seems to be feeling better. It's hitting the overload that it understands and it's starting to work. So now then, everything seems pretty fine here, except we've got query function, like this key any token string. And this key any is not quite right.

03:08 How do we know what this is supposed to take here? Like, I think what this is supposed to take, let's take a look here. This function, where are we even? Yeah, this context here is currently typed as any, but we're actually expecting something specific on here.

03:24 We're expecting that the exact type of this query key should be passed in here as a query key. And if we look at UseQuery itself, and let's actually dive into like, actually, no, not UseQuery result. We want to use UseQuery options.

03:41 And, oh, blimey, extends UseBaseQuery options, which extends a bunch of different things. Context options, QueryObserverOptions, TQueryFunctionData. Let's say QueryObserverOptions. This looks right. We want to find QueryFunction. And here we go. Now we have a QueryFunction, TQueryKey.

04:00 And we come to the correct point here, which is we have context, QueryFunctionContext, TQueryKey. And this QueryFunctionContext, it takes in a couple of different things. We've got a query key, we've got a signal, which might be passed or not, a page param and some meta here too.

04:19 So what we should do is we should actually pass in QueryKey, TQueryKey into there. This means then this key actually isn't correct. It should be CTX. And this CTX should be, what was it? QueryFunctionContext, and we can pass in TQueryKey. Beautiful.

04:40 So now then everything starts working. We've got our context typed correctly, which is the thing that we're supposed to be kind of like passing to this. So CTX is typed correctly. All of our tests are now passing because CTX, we can look at context.queryKey and it's an array of strings.

04:55 So if I pass in like a number here, then this is actually going to be string or number here, which is really nice. So we're getting all the inference working. And the thing that we're getting back from this Query.data, we look at that Query.data is the thing that we expect.

05:11 So Query.data is, let's just look at the first member of it, and it's ID and name there. Beautiful. So this is a really nice way that you can create a wrapper around React Query. Notice that we didn't have to go in and take in all of the type arguments.

05:27 We just selected the ones that we needed, and we found the right types, like inside the library, for like we didn't need to define another version of context or anything like that. We just use the types that were already there, and it seems to work really nicely. Well done.