Use Generics to Type a Fetch Request
The first step, of course, is to add a <TData>
generic to the fetchData
function:
const fetchData = async <TData>(url: string) => {
However, this creates an issue because fetch
doesn't have a type argument slot as seen in TypeScript's definitions inside of lib.dom.d.ts
:
Transcript
0:00 The solution here is to, of course, add a generic onto here. Let's say TData. Now there's an issue because we have data that's coming down here from this fetch. The interesting thing about fetch is that you can't really pass it any generics.
0:18 It doesn't have a type arguments slot. This is really frustrating and really, I would love for it if we could just pass it TData inside here and then it would understand and work better, but it doesn't.
0:32 This is going to be a problem that you come up against in lots and lots of, especially library code. Sometimes, especially if the library wasn't written in TypeScript to start with, they wouldn't think, we need to add a type argument here.
0:47 What fetch does return is, it returns a Promise with a Response and that Response, it has a JSON function on it somewhere. What that JSON function does is it returns Promise<any>. Here, what we can do, does JSON have a...nope. That doesn't have a type argument on it either.
1:07 This means that we will have an any inside our code base. Yuck. We do not want that, anys inside here. If you don't know the perils of any, it means that any access we do on it is not going to be type checked.
1:22 We need to find some way of reducing the scope of that any so it doesn't bleed into the rest of our program. What we can do...There's several solutions here. We can, of course, assign Promise<TData> like this.
1:37 Then what we'll end up doing is we have an any here, but it doesn't escape the scope of our function. If we don't return this, of course, then our any gets out into the world and starts causing havoc. Whatever we should do, we should trap that any inside our function.
1:55 Even when it's inside our function, it's causing a little bit of harm because we could do like data = null, for instance. Imagine if we had this is as a let instead. This just destroys the entire function, stops the test from working, but TypeScript won't yell at us.
2:10 We need to find a different way to do this that isn't using Promise<TData>. The best way I think to do is we could do it here. We could say data as TData. This means that the data does get inferred properly, but it's pretty similar to what we had before.
2:28 It just means that this is inferred as Promise<TData> in a different way. My recommended solution is to say data: TData here. This stops the any at source and we can say, sure, TData. Now there's no anys inside our code base, except in this tiny little function here.
2:49 We could even do this, where we could say, it returns Promise<TData>. Then there's not even any coming out of the JSON here. This is also inferred as Awaited<TData>, which is really nice too. This is a lesson about how you can use generics to really stamp down on those anys and stop them from even infecting a line of your code base.
3:14 Now, if we try to assign this to null, for instance, data = null, then this is going to yell at us because 'TData' could be instantiated with an arbitrary type which could be unrelated to 'null', unhelpful error message. What it's saying is, TData could be anything, why are you trying to force it to be null?
3:31 That's my solution to this very, very common problem. Make sure that you're really stamping out the anys at source, and replacing them using this syntax to make sure that there's not even a line of code that can use an any.