Lesson Description
The "Context & Selector Types" Lesson is part of the full, React and TypeScript, v3 course featured in this preview video. Here's what you'd learn in this lesson:
Steve introduces the Context API in React and explains the challenge of defining context and types before having all the necessary information and discusses different approaches to handle this issue. Steve also demonstrates creating a custom context wrapper to ensure type safety and avoid potential errors in the application.
Transcript from the "Context & Selector Types" Lesson
[00:00:00]
>> Steve Kinney: With the context API, we have a version of the problem we had with some async stuff before. At the time that we define the context and the type, we don't have everything we need yet. And again, depending on what you're doing, there's a bunch of different ways to kind of handle this, but we usually use create context at the top level, and then typically, a lot of times if that context involves a bunch of state, we'll kind of wrap it in this like higher order component that we then wrap around index or the rest of the application.
[00:00:39]
We've seen how this game plays out before. You know, a legit, totally reasonable way to do this, you can say, hey, this is a context that will either be like this plans context type, or undefined because we don't have it yet. Or if you don't like undefined, you can say, or null, I don't care which one you do. Because like, arguably, you will have it before it ever gets used. Like, it's one of those things where like TypeScript does not know, because if you just say this can be plan context type, you don't have like the plans yet from that useState hook.
[00:01:23]
You don't have, you know, like these set plans that need to be inside here to actually change all the states. You just don't have everything that you need yet, right? So you're like, it is totally this, it's totally going to be this shape, and by the time your app loads, considering it's wrapping the overall application, that will be true. TypeScript just doesn't know it, right, and it's not being like, oh, it's cool, let me look the other way, despite the fact that it is the language that gave us any.
[00:02:01]
It's like, uh uh, right? So like this you can't get away with. But the problem is we have a version of what we had with the async problem, which is if we say that this create context can create a plans context, that it's either going to be this like array of plans with these methods, or null, we will forever have a value that is either this object or null. Which means forever, every place that we use it, we'll have to check to see if it exists, which, fine, if it didn't almost always exist, right, because again, it is the very first thing that we will have before anything else in our application ever loads, since this will wrap the entire application, right?
[00:02:57]
And so, let's just talk about the worst way to do this, right, which is to do like, and I like, I have some ESLint rules that will stop me from doing that. That's one way to do it. And it's honestly, it's not that bad, right? Because again, any is not, it's really tricky. There are sometimes, especially if you're writing like some kind of library code, and you are doing all of the manual checks, like having an any so you can use some JavaScript features, are is really nice.
[00:03:39]
And like, it's really where the any gets dangerous is like somebody, whether it's tired you or somebody on your team, doesn't want to deal with stuff anymore and just throws an any in there so they can get the feature shipped and the ticket across the board, and now you live with this forever in the weirdest ways that manifest. That's where it's bad. So, I don't necessarily care about this.
[00:04:03]
I will say though, the slightly better way to probably go about this, not the good way, not the right way. Right, the good way, or good enough way is to just say, which is like because like, it'll never let you say null as plans context type that it won't do, but if you let it be as unknown for a second, means right now we'll let this value be this, right, but then it can only really get set to that again, like you're basically saying it's not any, it is about to be this.
[00:04:44]
Now careful listeners will realize that that's not that much different. Right, I'm basically saying TypeScript look the other way for a moment. I know what I'm doing, leave me alone, right? That said, this is what I do. Right, because I will show you the right way to do this because I am a professional, and it is my civic responsibility to show you a way that is actually legit. And then you have my blessing to go back and do this, right?
[00:05:11]
Or you can do something like, you can use like in type, if you want to insist something exists, you can use like an exclamation point at some point or another, be like, TypeScript, be quiet. I know what I'm doing. Now granted that, you know, with great power comes great responsibility. So like, if you say that to TypeScript too much, you've ruined the point of TypeScript if you're ever wrong.
[00:05:43]
So we will look at a way to kind of do this a little bit better, more better, right? Which is kind of, we saw before, TypeScript, if it knows something, like, is unreachable unless, it is okay then being like, okay, from this point forward, it is now a thing, right? So let's just make a kind of like, we'll start with a dummy function, dummy function. And we'll say that this dummy function takes a value that is either, let's just go with a string, or null.
[00:06:34]
Right? And now if we say like, let's just export it real quick, so I don't have to look at that line. What are you angry about? Oh, leave me alone. If we say, like when we say console log value, value is the string or null, no surprises there, right? If we said, if value equals equals equals null, return, well then value can only be one thing, a string. But this is, you know, kind of weird because if you look at the return value of this now, it's returning.
[00:07:20]
Oh, I guess we return void, so like that's fine, otherwise, let's return the string. So now we'll return either void or a string, or undefined or a string, that seems weird. The other way to like insist that of the two options, value is only one of them, is to throw. Right, and now we know that value is a string, otherwise this code would have blown up. To get to this line of the two things value can be, it has to be a string.
[00:08:12]
Great. And so what we could theoretically do is we can make our own kind of wrapper around create context, which is like, since we know, we think we know, we feel pretty sure that since this wraps our entire application, that we will have plans by the time anything that relies on this context ever loads, since it is the top thing wrapping the entire application. What we can do is kind of have our own little wrapper on create context that blows up on purpose if that's not true.
[00:08:42]
Now ideally, it should never blow up, but at least if that is untrue for some reason, it's blowing up somewhere really obvious. A lot of times if you get a, like if you have a value that you think has all these things, and it turns out it's either null or undefined, your code is going to blow up at some point. But it's going to blow up like seven components down, and you're going to spend like 20, 30, 40, an hour, right, trying to find it.
[00:09:15]
At least if it blows up at the top with an error message that you wrote, you're going to find the bug really quickly, right? And so you can create like a wrapper, for instance, like if we did something like export const, create better context. What I would probably do is like call this one React create context or whatever, but whatever. Create better context or something, honestly, it doesn't really matter.
[00:09:46]
And we'll talk about generics in a second, but I'll show you one, which is like, we'll have some generic, because that will be what we put in here. The generic is effectively an argument for a type. We'll say, we're going to pass in something here. I'm going to argue they should extend an object, it doesn't really matter. He says, and then they'll find out that it does matter momentarily.
[00:10:17]
You know, here's the problem. If you are using arrow functions in a TypeScript TSX file, you have to do something weird, which is either put a comma after it or actually put something in here. Otherwise, when the actual parser sees this, what do you think it thinks it is? A JSX component. And like it gets very confused, so if you actually say like extend something or you can put a comma after it, something that makes it not a JSX component, then it will work, otherwise things get weird.
[00:10:49]
And again, if this, if I'm about to show you confuses you or makes you sad or something like that, just do this, you're fine. I've never had it blow up on me. Bad things have never happened. This is for, I think the, for the purpose of like, no, I feel gross about this, I don't like it, it upsets me. I will show you the alternative, choose whichever one makes you happier, you don't have to do this one just because I showed it to you, do this one if it makes you, I don't care.
[00:11:19]
We'll create one, we'll create a context in this case. We'll say like, context equals React, I create context. Well, I really have to create context already here. And this one will say that it's either T or undefined, just like we had before, T or null, whichever one makes you happier. And that's very much what we had earlier. And then what we're going to do is effectively, we can like blow up when we go to use the context if it's not actually there, and thereby if the code ever runs, it's not like, oh, just pretend it's the context that I said it is, it is literally, we would know for a fact.
[00:12:04]
So I could say const useContext. So we create effectively our own create context and our own useContext. And we can go ahead and we can say, our other, I guess I could call it context, but like, at this point we use React's. Nope. Talking and typing at the same time is hard with that context we just created. If is null, right? Throw a new error. Something that you will know. A lot of times I, in my own custom error messages, I will put like an emoji or something, so that I can very clearly see that it was my error message, like, or something like that and not have to, like, you can always see it from the lines or whatever, which means by definition, this will always be, oh, nope.
[00:13:40]
Nope. There we go. Oh, you can't name the thing the same thing over and over again. Right, so like try to use the context, if for some reason it was null, blow up. Now we always know it is the thing that we passed in, and then we can return like. Right, now if we use this create context and the useContext hook that comes out of it, we will get one where we literally checked every single time to make sure it wasn't null, and we're not doing any weird hacks at this point.
[00:14:15]
We are like, in fact, like actually like making sure that it is the thing we think it is. So like that will get you one that then is actually type-safe and does all the things you think it should do. But now you have your own bespoke create content and your own useContext hook. If this grosses you out, and you're willing to have your own versions of the hooks, this will do the trick.
[00:14:42]
I just do this one. Like this is the right one, this is the practical one, this is the right one, this is the practical one. Choose whichever one you want. I'm choosing this one. I, for a while, thought I was better than everyone and did this one. I no longer, I've never had this one blow up, like there's codebases that I have like, never, I've, I don't work at that company anymore.
[00:15:06]
You can still kind of see it in there, and it's never blown up, right, because it's unlikely that it will. This will still blow up with a different error message that you could at least handle. I, this is, it's fine. But for the purposes of education, again, you can kind of create your own context, create a useContext hook that makes sure that it's not null, and thereby you know that it is the value you thought it was, and thereby it theoretically works.
[00:15:44]
Whichever one makes you happy. But while we're here, let me show you another like fun TypeScript hack, which is this as const at the end. Like this basically says if I, if I only returned it like this, it's like, oh, this is like some kind of array of like either whatever that object is, or like undefined or like whatever. If you say as const, it'll be like, yo, this is a two-element array.
[00:16:06]
The first one is always this, and the second one is always this. This is what useState uses, and what useDispatch uses, that's why you always like, you always know that you have the right type when you useState for like the count or whatever the value is, and you have it for the set, because they put this as const, and that means it's not an array that can change size, it's not an array that can change elements.
[00:00:00]
It will always be these, it's effectively a tuple or a tuple, depending on which way you like to say it, from other programming languages, but in TypeScript, that is how the React hooks work under the hood.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops