React and TypeScript, v2

Create Context with Generics

Steve Kinney

Steve Kinney

React and TypeScript, v2

Check out a free preview of the full React and TypeScript, v2 course

The "Create Context with Generics" Lesson is part of the full, React and TypeScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Steve uses generics to code a custom createContext method that will throw an error if the context value is not set. This technique eliminates the need to cast the types with the "as" keyword by taking advantage of generics and closure scope.


Transcript from the "Create Context with Generics" Lesson

>> Now that we know a little bit more about generics, we're gonna look at one way to solve for that createContext issue where we had to use as to force it. Pulling together a lot of the things that we've all picked up over the course in the last few hours together and kind of pulled them together to solve for this issue.

Now that we're armed with generics and we can take a run at it. So I have this in place for now, I'm gonna leave this code here for a second until it gets in my way. One of the things, actually, why don't I, I'm gonna make myself a fresh file somewhere right next to it.

I'm gonna make my own createContext, File. Look at that. I'm only getting yelled at cuz it doesn't like empty files. That's fine, I will make it not empty soon. So, why right now? There we go, create context. This is gonna be in addition to React. And sometimes this is a useful technique for other things as well.

Let's actually call it the right thing. Talking and coding, createContext. Awesome, everything's happy with me again. There's some leftover stuff from previous typing, okay. So, we want to be able to basically know about what the type should be at the same time that we have the information available to us.

So like I said, this is gonna pull in a bunch of different concepts, most notably it's gonna pull in what we just learned about generics. It's also gonna pull in what we learned about reducing some of the cases, right? We saw the action type which is the more of them that we call it out and then return values from the less options TypeScript thought it had, right?

So really what we need is a way that if it didn't know what properties were on this object, that by the time this function returns, it is confident, right, that it has the right thing. And there are some trade-offs here, and we can talk about them. There was a perfect solution.

I would argue that, like most things, the way I feel about performance, if there was a you should always do this, they would have just put it into the framework, right? Everything they could have put into a microchip, they did. And every level of abstraction up, we're only left with the weird edge cases and the specific applications of that at the very end.

So welcome to being a software engineer, what do want me to say? But the goal here is to kinda use a bunch of these different concepts to come up with a situation where it's like, all right, here's the context and I know that the value is what it is.

So, we're gonna use a generic here, we'll call it T, you can call it C if you wanted to. I don't know, whatever. I'm gonna do in the color app. It actually, literally this technique, I could have pulled up any app, it wouldn't really matter. I'm gonna do it in the color app cuz I should fix it in the way I did it and then implement stuffs in the items app so you can do it there.

But it's gonna be the same in both apps, so, doesn't matter. So we're gonna have this T, and the only thing I really care is we saw that value, every time that we used it was an object, right? But it was either wrapping all the stuff for the items in that packing list app, it was either wrapping the hex color and other stuff there as well.

So I think that's a fine heuristic. We're gonna say T. But I don't want to deal with undefined or no or anything like that. I wanna at least assume that I'm gonna get some kind of object, even if it's an empty object. I wanna assume that this is some kinda object, even if I don't know anything about it.

So let's say that T extends some kind of object, right? The very least, it could have more keys in this object. But it at least as to have that rough shape to it. That feels like a fair constraint to put on. They're tacked into the context API, you can undefined, I don't know if you can put it a null.

Yeah, yeah, I did earlier. You can put it in null. But we're gonna say that if we're gonna go through all this work, it's gonna be something that we can swap values in and out of. It was just a static value, like a string, that we don't need to do this at all, right?

We only need to do this cuz we didn't have dispatch of the key on that object yet, or setState or whatever. So let's assume that we're pulling out this trick, it's gonna be an object. So that's step one. So then we wanna create a context. I'm gonna not pull things off of React because if I say import in curly braces createContext from React, I'm gonna just recreate that bug I had with RGB in the color app, except maybe redefining a constant, I'll get yelled at.

And I don't feel like coming up with a new name for this. So we'll say, we'll make a context inside this closure scope, right, inside this function. And we'll say, we'll make a context, before I called it colorContext or itemContext, this is a generic helper. We'll say it is React.createContext.

And we're gonna say that we're gonna give you a type that is either the one that we want with everything batteries included, dispatch, all the fun stuff, or, this guy again on the generic. It's either gonna be T or it's gonna be undefined, right, which is what we did, we either put an empty object or we did something.

It doesn't really matter, right? The other half of this or statement could probably be anything you wanted, because we're gonna figure that. Undefined is nice and easy to check for. So we'll go with that one, you could put, if I put a nan, I don't know. We'll say it's either the one we want or it's undefined, which is not totally different than the problem statement we had earlier.

And for now, We're gonna say it's undefined. Now, I need to make the like abbreviation second. I get bitten by it all the time, it's never the day I wanna do it. Maybe tonight is the day I wanna do it, we'll see. Cool, so we've got this context.

And now this could be anything, we don't know what it is yet. And so what we wanna say is, we also need a useContext. We basically need to create our own createContext and our own useContext. And they also need to live in this little encapsulated bubble. Cuz the problem we had before is, if you remember, we had context up here.

And that was fine cuz we can then export stuff. So we had context up here and that was fine, cuz then we could export stuff. But then we can only make the hooks inside of a React component. So the fact that they couldn't exist. Now, in here, we've got a function scope that is encapsulated, right?

And then we can package up in certain places. And so what we can do is let's create our own useContext hook here too. All right, and useContext, this is gonna be kind of a wrapper around the one in React, just like this is, right? We're basically using closure scope to solve our own problems, even though it created problems for me earlier.

And I've forgiven it. So we'll say that we'll have, I got to call it something. We'll say, calling it lowercase context will confuse everyone. It'll make us all sad. I can't say uppercase and lowercase letters with words. So we'll say useContext with this one up here. So, this one implicitly already knows without any module exports, before we had an export itemContext, we had an export colorContext.

And then in the one we pulled in React and we pulled in other context. Here, we have the advantage of, we don't necessarily have to do that because we are living inside of a function and we don't get yelled at by React about using hooks outside of function, so on and so forth.

So we've got that in place. And so you're like, okay, cool, cool, cool, what if I, no, we don't need that in braces, useContext, it's just that one, yeah. useContext, we'll have the CTS [SOUND]. And then ideally return the context, you'll see why this blob at me a second.

And then what we're gonna do is find a nice way to say, I wanna be able to give you that useContext hook and the context provider so we can wrap our application with in one package that you could import everywhere that you want. You should spell return right, that's key to making this work.

Right now, this could still be, it's almost there. There's a few little tweaks we need to do. We're gonna have some kinda T, it looks like we're gonna have an array that could be a bunch of things. It could be a function that returns context or undefined, that's our useContext hook, or you could come back here.

Or it could have a React provider that is T or undefined, and that will be an array of infinite number of these useContact hooks and context providers. Well, one, we know that if we can lock down an array, it's not an infinite array of any of these, right?

It can be a static, two things. The first one is this, the second one is that. That's just simply putting. So now this is not an array of many of these things. It is an array, just like useState, the first thing is always the state, and the second one is always setState.

Here, we're gonna say now if we hover over it. Okay, cool. This is getting better already. Still, we haven't solved the core problem yet. We've just moved the problem around a little bit. And so here we have, all right, we've got to, it should extend some kind of object, right, and it's gonna return, there's two element, arrays, tuple, tuple.

I have never decided which one I like, they're both are appropriate to say. I think I'm gonna say tuple cuz I'm from New Jersey and it just feels right. The first one is affectively the same signature as a useContext hoop or the value of the useContext, right? It's T or undefined, which is, okay, and the provider has T or undefined.

So we know if we can end one of these branch timelines, we can eliminate one of these options, right? And so really I wanted to say, yo, this is always gonna be T and I want you to feel confident that it's always gonna be T, right? And previously the way we've done that is return out early with a different jsx element or something like that.

Unfortunately, if we return out early and we don't have a value, that doesn't do us much good. So the question is, and this is a JavaScript meditation for a hot minute, and 50 points for anyone who can guess it. There are three ways to get out of a function, one, you run out of code, like line 13.

That's one way to end a function, get to the bottom of it. Line 12 is the second way to get out of a function, return, right? I could return on line five and be out, right? There is a third way to get out of a function that does not have a return value.

>> Break?
>> I think break gets you in a loop. I don't think break gets you out of a function.
>> Throw?
>> Throw, yeah. If I throw an error and I don't catch it, out we go, right? So what I can do is say like, and listen, if for some way, somehow, have error boundaries set up in your React app.

If [INAUDIBLE] yield, I think only gets me out of a generator. Throw and error is, in my opinion, the best way to do this, right? So we're saying there shouldn't be a world, we saw it before where our context is undefined. If so, let's deal with that, right, and say that we are going to hit an error boundary, catch that error somewhere, deal with our application code to say, if you use this, we will confirm that T exists so that for the rest of our application, you can also know that T exists.

So we'll say if ctx, I'm not gonna talk ill of the Go programming language, I don't like short variable names. I don't even like it in my types. It's the convention, I live with it. For me, I'm cool with a variable name. That's 42 characters long. I have autocomplete, you know that I like my autocomplete.

All right, so if ctx is undefined, right, then what we're gonna say is, and you can get as fancy as you want here. I'm gonna make this a template string. The most likely case of this happens is the value never got passed in when we set it up before.

I've seen worse error messages in my life. Cool, so now we're gonna create the context, we're gonna see, did anything come out of this context? If something came out and it's not undefined, and we get past, really depends on what we want to say we get past. Don't wanna say we get past, either we get past, the condition where we get past the throw, it's really a philosophical question.

But if we get through this conditional statement, right, cuz up here, ctx, it's T undefined. We're saying, you, if you're undefined, yell at the person writing the code, get out, which means by the time we get here, it can only be one thing, right? So now, if we hover over this, this is a useContext function that will give you T, it'll throw and the provider itself can be T or undefined.

But we know that when you pull the value out of this, we're gonna be good to go, right? And now we can actually have that typing from the very beginning. Yeah,
>> Is there an insert in TypeScript we could replace them with.
>> So TypeScript itself does not have any powers to do anything in your code.

When you like run the build process, it's gone, right? It's all JavaScript at the end of the day, it is only kind of hovering around your JavaScript. It cannot meddle itself in the affairs of models, right? It has to kinda just hover over and check to make sure everything's okay but it can't actually do anything yourself which sometimes is annoying, right?

I could make a type out of a value, but I couldn't make a value out of a type cuz TypeScript cannot impact the world of JavaScript, right? It lives in the shadows. Cool, metaphors I did not expect to pull in today. But here we are.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now