React and TypeScript Context API Edge Cases
This course has been updated! We now recommend you take the React and TypeScript, v2 course.
Transcript from the "Context API Edge Cases" Lesson
>> We have one last thing we're gonna cover here. And that is taking a little bit of what we learned and applying it back to some of the stuff we were doing with the context API. And I told you earlier that there were effectively three ways to handle the fact that we didn't have everything we needed for the context object in time.
[00:00:29] And I told you about two of them, right? And so we're gonna talk a little bit about the third and we'll see the code here, I'm gonna talk about it. And then we will go and implement it. But basically, when we said hey, it could be the RGBContext type or null, TypeScript got angry with us.
[00:00:57] Well, since it could be null, I'm gonna need you to check to make sure it's on the value object before you use it constantly. And that seemed tedious, so then we use the as keyword to say, listen, we know that this is going to be a context object almost immediately, trust us, everything's cool.
[00:01:19] And like I said before, that's probably okay, but you did take on a certain amount of risk. So if you're like, I'm not comfortable with risk, that's legitimate and probably wise. So we can look at this pattern here, which is to have our own abstraction around createContext. And what it will do is it'll create useContext one that'll basically anytime we try to use the context, it'll make sure that it's defined first.
[00:01:50] So basically, we're taking all of those, like I said before, we got to tell me that the RGB values in the context is set before I let you use them. We just basically have an abstraction around useContext that automatically checks. We just use that all over the place, and then everything will just kind of work.
[00:02:06] And there's some trade offs here as well. So we'll go ahead and we'll take a look at that. I wanna talk through the code just one time and I'll kind of explain it again, as we go ahead and we implement it, which is this idea that we've got this generic in here.
[00:02:25] And I wanna talk especially about this one right here first. We're gonna say that we're gonna have a generic, and we are going to insist that eventually, when the context gets set, that it is an object of some kind, right? It's a key value and it could be any object, right, cuz this is this is pretty generic helper or it's null, right?
[00:02:50] And that's what we did before in that first example. And then we'll start out with an empty context. And then basically, we're gonna always check to see if it's undefined and throw an error. If it's undefined, which will appease TypeScript that we checked to save was undefined first, before we used it, and then we will return a tuple or tuple, that's up to you, of our own useContext and that context provider.
[00:03:18] Effectively wrapping that check for undefined in a helper. And we'll use that instead of the built in tools that will then kind of talk to react.createContext. All right, so let's get that in place. Just give me one second to pull the right code sandbox, and we'll get started from there.
[00:03:36] This is the one we got, with our color adjustment and everything, we've got our context, great. So this is the one and I will hide everything else right here, where we used as, we wanna get rid of that. So the first thing we're gonna do is we can do it in this file.
[00:03:51] I'm gonna make a new file just so that we're not scrolling up and down too much. And so I'll make a new file here. I'm gonna call it createContext. All right, it's got React in it, my god, TSX, Fuck this. All right, so we've got this createContext. And we'll kind of type in the code that we saw before so that we can just, again, explain it one more time as we work through it.
[00:04:21] So we'll import React. From React, and we're gonna export this createContext function. I'm gonna use a function declaration cuz just to be friends of my function declaration fans. As before, we can say T, we can say E in this case, let's use T because, again, it's just a name of variable.
[00:04:47] It has to extend an object, or can be no. Just like we saw before, this is like in that very first example that we did. And we're gonna use react.createContext. And this way, T is gonna be whatever contexts we sought to create. So before, in this example, it was RGBContext.
[00:05:09] But this helper method wants to be generic enough. You wanna use it again, for themeContext, cool. I don't care what context shape you eventually want, you can pass that in here, and we'll be able to support it. So we'll have context, and we'll say react.createContext. Yeah, I was either, gonna get it this time, right?
[00:05:38] Or undefined, this means that we don't have to define it on the outset, which is what the problem we ended up with before was. So we'll say undefined, So this is effectively that line of code we were playing with in the first example. And now we're just gonna put in our check, we're gonna have this a special useContext.
[00:05:59] So we don't have to check in every component that uses the context. So this is our bespoke one or artisinal useContext if you will. We'll say our c is react.useContext with the context we just created in this closure. Now we'll check just to make sure it's defined. If c is undefined, we can't do anything, so- Throw a new error or handle it gracefully.
[00:06:37] If you know what the fallback behavior should be, it is probably better to implement that here than to just throw an error, this is somewhat generic. You must have a defined context, how about as a value for Provider. All right, sweet, so we've got that there. And now we'll say otherwise, cool.
[00:07:15] If that's not the case, you can return that context object out for us, right? And now, that's our useContext that we'll use in our components, this function right here. And so all we're gonna return out of this is that useContext function we just made, and also context.Provider. I guess you could return the whole context as well, but all we can use it for is context or the provider rather.
[00:08:04] In this case, I think you can actually see it here. Yes, in terms of read only array, so you cannot push anything on this array. You cannot pop anything off the array, you will always get these two values out of it cuz we have that in place. So now, we'll go back into our context, instead of using react.createContext, we wanna use our brand new createContext.
[00:08:29] So we're going to context.TSX. And now we're gonna pull in, let's see if, Let me make sure I exported that as well. I did, sweet. We will, Import, createContext from ./createContext. And we will swap this one out. And this one now no longer requires an initial value. But we do need to tell that generic, which we defined as T, what type it should eventually expect, which is gonna be this RGBContextType.
[00:09:14] So we can swap that in. The other thing we're gonna get out of this instead of just an RBGContext, we're getting that useContext and we're also getting the provider in this case. So we'll take the provider out of that. And now here instead you can see that we no longer have that, so we're gonna place this in here is instead, And swap it out here.
[00:09:45] Everything's pretty happy with us in this file. And we've gone ahead and built that. So now that's still wired up from before, and so all of that should be fine as well. And you can see that it's not angry that didn't initially have the value cuz it's gonna check.
[00:10:01] It's not expecting a value and it will later check, we'll see that in the components in a moment. So now we can go into, I believe color adjustment, and then color swatch for the two that used it. You can say this is no longer a thing. So here we'll grab, actually we need our, Our useContext.
[00:10:28] If we need to probably export that one from before, Pull that out of this file. Is this the right file? Hold on, let's try on a second. Or we can do actually, as we'll export this here, I did, I'm ahead of myself. It's rare that you live code ahead of yourself to think about there.
[00:11:04] So we'll go and we start on color adjustment, we'll do that one. We're gonna pull out that, useContext, not gonna use React anymore. So we've got our new useContext. That one has the context that we created in the closure, so it no longer takes one. So now you can see that we basically got the best of both worlds.
[00:11:30] We got a situation where we did not need to have the full known context object when we created the context. And also we don't need to check to see if the values exist in every component because we have an abstraction that will check for us every time we seek to use it.
[00:11:52] Now, you might be wondering, is there a trade off? There's a small trade off, which is instead of being able to use react.useContext and pass in the, like the normal API that everyone on your team knows how to use, each different context that you create is going to create its own useContext that has the right context and a closure.
[00:12:16] And so for every context, you got to use the right useContext, but you have to pull in the context from those files anyway. It really just becomes a documentation and education effort, right? And that's still a trade-off cuz again, mistakes can be made around, you now have a function that kind of behaves mostly like the one everyone knows and loves.
[00:12:36] These are some of the tricky decisions that you need to make as a team and to think about as well, right? You've gotten the kind of full type safety this point but with other trade offs and risks. And I unfortunately cannot fully advise on that one, that is kind of up to you as well.
[00:12:54] So we now have our better useContext in place and we can do one other thing that we learned from a previous example that we didn't talk about cuz it just wasn't time yet. But just to kind of tie it all together, we have this adjustment input props and these proxy to a bunch of input fields.
[00:13:15] And they take ID label, which is not on a traditional input element, right? We're using a regular label tag, we're making a div that has a label tag and a value and it's using the right HTML for and everything along those lines. We can actually say that these adjustment input props, and this is how you would do something we saw before but with an interface, we can say extends.
[00:13:43] This is a utility type that is built into React in this case. All right, so now our adjustment and for props take those that we saw before, but they also support everything that an input property supports as well. And then we could then spread those over the input field and pass them through in that case as well.
[00:14:08] So yeah, we've solved for that third case on the context API, right? So we have the three hypotheses. Like I said, there's not necessarily a perfect answer in that case. But you can kind of choose which one is the right one, given the trade offs you're comfortable making as a team.
[00:14:28] Typically, we chose to use the as keywords, we chose the second hypothesis. Part of that was just the time space continuum in that case. I can say that, yeah, just say that we have shipped app without any bugs ever would obviously not be true, but that one has not bitten us in three years, some wood I can knock on, right?
[00:14:50] So far that has not been less than three years. So I feel yeah, fairly confident recommending that when don't ask me, if it doesn't work for you. I've given you the warnings and explain the trade offs. If you wanna play with the stove, but yeah, so those are three ways to handle the context with the different trade offs as well.
[00:15:09] But TypeScript can support and I think a new ones understanding how React and TypeScript work together, help to figure out some of these edge cases. It's not that things necessarily have to be hard in TypeScript, it's just simply that there might be a way around it, you just kinda have to get a little bit more comfortable with some of the nuance.
[00:15:27] And also understand that React also brings a set of utility types and types around working with components to it as well in addition to what you get from TypeScript. So in this case, one of the questions in the chat is, why did I choose react.htmlprops versus componentType? Cuz this case, I want the actual full, the prompt definition for the HTML element itself.
[00:16:00] I don't necessarily wanna read it directly, I wanna go ahead and pull me out this one from the built in HTML elements as well. In fact, I don't wanna support any other component type in this case.