This course has been updated! We now recommend you take the React and TypeScript, v2 course.
Transcript from the "Higher Order Components" Lesson
[00:00:00]
>> So we talked about higher-order components. High-order components are just a pattern in React that you might be familiar with. Particularly in larger codebases they will kind of take common pieces of state management and pass them into components. Some of this you can do these days with hooks.
[00:00:18] Hooks and higher-order components aren't mutually exclusive. I use hooks and higher order together because it allows you were separated the data and state management piece of your component with the presentational piece. You have a hook in your component and you want to unit test, well all of a sudden you've got to mark out a bunch of stuff if it's common APIs.
[00:00:38] And you'll have to figure out how to simulate all the things versus you pull that out into a hardware component that does maybe the API fetching, all that kind of stuff, passes props into a presentation component. Easier to test, easier to tune for performance if you want to memorize certain props so on and so forth.
[00:00:55] It's also a really fantastic way for us to get a little bit more familiar with kind of some advanced patterns using TypeScript and React. So we're going to take one of the examples from earlier and we're going to create a super simple higher-order component around it. Just to kind of see the pattern in practice and then learn a few things about how to do some of this dynamic typing with TypeScript around React components.
[00:01:22] So we're gonna look at those character cards that we saw earlier. And a higher-order component just wraps a traditional point and takes some of the stuff you might have done with the hooks in state management and passes it into that presentational component with props. So if we look over back into our character card, go into application over here.
[00:01:43] We're gonna kind of take this piece out, and we're gonna be able to kind of pass it in to the application component, or the character information card component rather. And push all of that in and be able to use that instead. So what we're gonna do is we're gonna create a component.
[00:02:04] The pattern is to call it with character, or with whatever the data you're passing in. So if it is a component that needs the current user, cuz so many things need the current user from what you display for log in log out to like whether they have the permission to view a thing.
[00:02:20] It'll be like with current user, with user or something along those lines. For this one, we're gonna take the random character, and we're gonna use that, so we're gonna call this one with character. And there are some pieces of higher-order components that we are gonna kind of like just brush over a little bit.
[00:02:34] Because it's more like the kind of nuances of making sure the display name looks in your React tools correctly. And that's kind of tangential to the part we are trying to focus on which are these TypeScript patterns. So we have this const with character and we'll just do a naively simple version, it's gonna take some React component as an argument.
[00:02:54] And we'll give it a type of any for now. We'll obviously fix that, but just to kind of get the basic pattern in place. So it takes any and its gonna immediately just return that component. Obviously this is not the best higher-order component in the world, but we'll get there.
[00:03:12] And then, so if we wanted to kind of have a version of a character information card that just had all the data passed in, we might say something along the lines of constCharacterInformationWithCharacter, right? Just simulating that is the combination of our character information component and with character higher-order component as well.
[00:03:38] And all that will be is this with character and we'll pass in the character information component, right? Right now, that doesn't really do much since it's just basically a pass through. It's actually worse than what we had before at this exact moment, because the hierarchical component has the type any and it's not actually passing anything through but we'll fix that.
[00:03:58] And then we're just gonna kind of swap it in here. And TypeScript's gonna get angry with us, but like I said, we will address that in due time. We'll say that's gonna, it won't even need the character because that's gonna be passed in. So there it is. If we threw character in here right now, TypeScript would now be angry, even though this is a pass through component.
[00:04:21] Because it's like, I don't know what that prop is, right? So we've got that, and now we need to flesh out the rest of this. Which is what a hierarchy component should do is it takes a component and returns that component with some of the props passed in already on our behalf.
[00:04:41] So the first thing that this is really gonna need to do, we'll kind of comment this line out for a second, is it will return a new component. Which again, in React these days is effectively just a function, right? Back in the day when you walked up hills both ways to build a React application, you might have put a class inside of this function, that extended React component.
[00:05:05] So we'll return a function and that one is we're gonna start to pull up some of our kind of pieces from up here. So we can for instance, pull these pieces up. All the state management pieces are gonna get pulled up, so even the fetching part. And so now if we wanted to reuse this, we can wrap any component in with character and we would get all of the kind of loading and the character itself, and the fetching and all of those things as a part of the deal.
[00:05:40] And we can say, let's go ahead and I'm gonna fork this real quick actually, we'll just save it. So we get some formatting and right now things are a little sad with us, but we can fix that. I gotta move some stuff around as well. This goes inside of the component, great.
[00:06:01] And then we'll say, If loading is true, we can just return null or we can return the loading component. And otherwise return whatever component we passed in with the character. All right, so now we can create all sorts of different components that will automatically show loading component. If they're loading to automatically fetch the character, they'll pass it into whatever component we choose to wrap, great.
[00:06:34] And there we go, so TypeScript is a little angry right now, saying it's any it's never read. Yeah, that's all true at this moment, we will get that kind of all working as we go along. And that we can use that basically solely put this in here. We don't need the rest of this, this is all being handled by our higher-order component, awesome.
[00:06:57] Right, so we've got that in place, now we need to actually flush out the rest of the higher-order component. Now, we talked about generics before. Since this can wrap any component, that giant asterisk that I'll talk about in a second. A higher-order component traditionally in regular React, can wrap any component and pass that stuff in as a prop.
[00:07:19] However, one of the promises of TypeScript for us, is the idea that we want to make sure that maybe the component that we're wrapping accepts in this case a character, right? So really it is not just any component will be a regular JavaScript we wanna make this a little bit better.
[00:07:34] We wanna say that this with character will take any component that takes a character prop, it will be the one that passes that in for us, right? And then we wanna return a component that maybe takes all of the props it originally took minus the character component. So if you think about those utility tests we talked about earlier, we have roughly all of the pieces that we need to do this.
[00:08:00] We just kinda need to like tie a bunch of the stuff we learned together. So this sounds like the kind of job for a generic. So we could say with character, in this case we'll say T, and one of the things I'm gonna do is, I'm gonna switch this to a functional component.
[00:08:16] One of the reasons is, the compiler is somewhat confused by this otherwise fine TypeScript syntax. The reason for that is, since we are writing JSX, it is really hard for the parser to figure out whether or not we are saying const with character. And parse is going along sees equal, sees open angle brackets like you're gonna write some JSX and then it gets very confused.
[00:08:41] One of the ways to handle this is if you put a comma here, then boy, that must be a TypeScript generic and not a React JSX component. I don't really like that. And if you decide that you're cool with this comma, you're gonna find that certain formatters are gonna fight with you.
[00:09:02] Prettier is gonna be like, what's that comma? I'm gonna take that comma out for you. And then the TypeScript JSX parser is gonna be like, I don't know what's going on anymore. So even if you like arrow functions, one of my recommendations here is just to, when it approves you, to get more traditional function declaration like this, a function declaration.
[00:09:25] So we've got this generic and now we can say that the component should be a React component type, that is based on T. And we saw that before when we did that utility types exercise, React.componentType will go and find out the props of a given React component that you pass in.
[00:09:54] And so we saw that before when we use it with the current user in the previous example. And we also saw that these functions will be pretty smart. So what happens is, you're gonna pass in a component as the argument to with character. It's gonna look at that component, use the utility type of react.component to figure out its props, and assume that we're gonna use a component to define T in the rest of this.
[00:10:20] So then, really, we return a new component that takes the same props, right? So here we are reading the props of whatever component we pass in, and we're returning a new component that takes the same props for now, right? So we've got that in place, and you can see TypeScript has started to get a little a bit upset, it doesn't really know much about component just yet.
[00:10:49] Here we're saying, you told me it took the same props, where's my character, right? And we know in a higher component that like, listen, we're gonna give you a character you don't actually need to take a character prop anymore. However, this component could take other props that we wanna pass through.
[00:11:03] So the mission we need to solve for is, we need to return a component that takes all of the props that it originally took., minus or omitting, the ones that we know we're going to pass in. So we need to figure that out. This is totally at this point, a TypeScript problem, that we effectively have the higher-order component in place.
[00:11:24] So we can figure out, okay for the higher order component, what are the props that it defines. Now in this case, it's just character, right? If it was just going to be current user, but maybe other things got passed in or something along those lines. But here we know that whatever component comes out of the with character higher-order component, it's gonna have a character type.
[00:11:48] So here we'll say character, CharacterType. And there we've got that. So now we've got this type here and so what we wanna do is we wanna return a new component here. That is, like I said before, all of the props that originally took, minus the ones that we know we're passing in ourselves from this higher-order component, right?
[00:12:12] And so we saw this before, this is using our good friend, the omit utility type that we played around with before. So here we'll actually say, this is omit from the type of the component we passed in, all of the keys of with character props, right? I got close that.
[00:12:37] So this is our new, we're basically reading the props of whatever component gets passed in and dynamically generating a new prop type. That is, again, everything that it originally took minus the stuff that we know we're passing in, right? And these are the things where I think when I was learning TypeScript and React, I was like, TypeScript must be bad cuz this stuff is hard, because I didn't really understand the stuff at the time.
[00:13:01] And then as you kinda dig into, you realize that you can do most of the functional programming things, most of the kind of normal React patterns. You just need to have that kind of both understanding React, obviously, understanding of some of the basics of TypeScript. But then also understanding the things that come with React's types, right?
[00:13:19] And that's one of the things where the stuff isn't really covered super well all the time. You can find a really great tutorials on React, really great tutorials on TypeScript. The intersection is a little, sometimes there's not as much in there. So we don't have everything we need.
[00:13:31] We've solved for the red squiggly lines, cuz it's no longer expecting that there's a character type passed in there. Because pick is the opposite of omit, and so we have effectively, if this thing took additional props like we pass in, we can redefine it in a second if we wanted to.
[00:13:48] But this is happy, but now this one's angry because it doesn't really understand what's going on here with component. So we can fix that as well, because it's like okay, you said maybe this takes other props, TypeScript doesn't know because we're using with character information. This higher-order component, can be working with any, at this point, any prop or any component that takes a character prop.
[00:14:13] So it's like, yeah, you might use this other places, I can't guarantee you that safety. And so that's problematic for me, and that makes sense. And so we just need to solve for that as well. So we're gonna say we're gonna pass in. So normally we can say, for all of the other props to go in there, right?
[00:14:35] But we need to say, Props as T, so we're saying hey, here are the props you expect. But remember we had to use that as keyword before, to effectively say, I know the character is not we're gonna pass character right next to it. So we're gonna say listen, the rest of the props that are coming in here are totally fine.
[00:14:56] We're gonna pass in character, and you can now see that all the rest of the props this component will take, or any component wrapped in this higher-order component, will get all of its props, will pass in character as well. The other kind of thing that we can do here is just to verify that this works.
[00:15:15] We can go ahead and take a look when you say something along the lines of like, okay what if I give you this a character? You can see already, no, it doesn't take this anymore does not exist on this type. So, now we have a new component which dynamically no longer takes the character, even though it'll take everything else this component took.
[00:15:38] All right, and so this is a powerful pattern for one, see how these utility types you can dynamically create types. Two, just see how you can separate the state management from your React component. Now you can use the same functionality everywhere. Yeah, you could possibly also load this into a hook, but then you'd have to figure out how you're loading component and stuff along those lines.
[00:15:56] It's still an important pattern, but using some of those techniques of both reading the props of another component right here. Being able to dynamically generate a new type based on that, and then the ability to kind of say we know what we're doing with some of these things to help TypeScript along the way.
[00:16:15] Because we are very intentional about this. Can you use as, as a hack? Of course you can. Should you? No. And so it's one of those things. But when you need to inform TypeScript that you roughly know what you're doing, this will allow you to get a sense of, I know I'm going to pass it in directly two characters over.
[00:16:40] And it will be all the things that this component expects.