Using Type Arguments to Create A Strongly Typed Context
The first thing to do is add the ability to pass a type argument into createRequiredContext
.
Add a Type Parameter
To do this, we need to add a type parameter T
and since we are working with a TSX file we need to separate the type parameter with a comma:
const createRequiredContext =
Transcript
00:00 Okay, let's kick this off by noticing that we need to be able to pass a type argument into createRequiredContext. So let's do that first. Let's say we need to be able to pass in a T here. Oh, of course, there's a TSX file. So let's just add this comma here. And now this solves a couple of issues immediately.
00:17 We notice that useUserReact.provide a null or this function that returns never here. This is not very convenient. What we need to do is we need to basically mark this as const, as we've seen before. Now, this means that useUser is actually a function that returns never, userProvider is a React.provide a null.
00:39 Okay, so we're getting somewhere, but not very quickly. This createRequiredContext then, as you can see, this T is actually declared, but its value is never read. So what do we want to do with this T? Well, we want to make sure that this context is actually strongly typed. Currently, it's only null here. And this means that this useContext hook actually returns never,
01:00 because we're checking to see if context value is null. And if it is null, then we throw an error. Okay, so that means that actually this function is never, ever, ever going to complete, which is why it's inferring never. Very clever TypeScript. So how do we make sure that this is working then? This createContext, when we create this context, we actually do want to still pass in null.
01:22 We never want to accept an initial value here, because the value is actually assigned in the context.provider. So the best way to do this then is to pass in a T here. But we get an error. And the error is argument of type null is not assignable to parameter of type T. T could be instantiated with anything which could be unrelated to null. Yeah.
01:43 So how do we solve that then? Because this actually, if you notice, that now everything is working. Everything is working. So this is really doing well. This means that our useContext here is basically saying, okay, this context value is going to be T.
02:00 And if context value is null, then throw an error, which means we actually always get a proper result out of this. So how do we get around the fact that null is actually like not something that we should be able to pass in here? Well, we can say null as any. You know, we can quiet the error down that way.
02:17 But I prefer this solution, which is actually in this type argument slot is to say T or null. And now if we say T or null, you know, like this context value could be T or it could be null. What this means then is it sort of justifies the construction of this function.
02:35 Because it means that we now can say T is basically going to be like it's going to return T, but it's going to filter out any nulls from it. If we hover over this, then you can see that actually this changes everything, because now this could be T or it could be null.
02:53 And now this user is name string or null. This theme is primary color or null. So just by adding this back in, we're sort of building on the inference here. We're letting TypeScript do the hard work for us. And really, we were just able to add in kind of like three little signatures here. And TypeScript was able to figure out the rest. You can be more explicit.
03:14 If you want to, which is you can say that useContext always returns T, and this always returns, you know, T, ReactProvider or null, or you can mix and match these as well if you want to. So we can say as const here if we want. This, I think, is quite useful because if we actually remove this, then it's going to error and it's going to say T or null is not assignable to type T.
03:34 So I think then this is maybe or certainly this I think is the best solution, this combination of them. But if you want to be really explicit or your TS config says you should be doing like adding explicit return types, then this is a perfectly valid way to do it.
03:48 This means that you get around this sort of annoying situation I'm sure you might have been in before, which is where you create a context and that context, you don't know what to pass in it. You know, you like you have like a default value that maybe you can put in there, but actually you just really don't want to use it at all.
04:05 And really, you just want to pass in null, but the types don't let you. But this is a beautiful way that you can do it without casting and while still having strong access to this value throughout the rest of your application.