Strongly Typing Lazy Loaded Components with Generics
When interacting with something that requires strong typing, it's important to understand the constraints from the third-party libraries that we're working with.
In this case, let's start by taking a look at lazy
.
Navigating to the type definition for lazy
by CMD + click
in local VS Code, or
Transcript
00:00 Okay, when we're interacting with something that we want to strongly type, it's best to look at the constraints from the third-party libraries that we're trying to interact with. So let's take a look at Lazy here. Lazy takes in a component type, Any, which as we've seen is basically just a kind of a union type between a component class and a function component,
00:19 each of which takes the same props. And we're going to need to know about those props because we need to type our kind of lazy load component down the line with them. So we need to somehow kind of capture the thing that's being grabbed in. So loader, now what is loader going to be? What is this, I wonder?
00:37 Like if we hover over this loader, it's currently typed as unknown, but this is a function which returns a promise of a component. So let's start there. We know this is a function, takes in no arguments, don't need to pass any parameters or pass any arguments,
00:53 takes in a promise with a component type. Now that component type, let's just sort of mark it as Any for now. And I'll also just import that from React. Now what we've got, this is starting to work and we've got some odd stuff here. Okay, so this is not assignable to component type Any. In fact, you know what this is doing?
01:13 It's not actually importing a component type. It's actually importing an entire module with something on a default export, which has that. So actually, this isn't going to be an entire module, which is just one component type. It's going to be an object with defaults, and that's going to be the component type Any.
01:30 Now that has started to go away, and this is starting to look good. We've now got our lazy loader function working. And in fact, you can see if we go here, this factory is exactly what we want to be doing here. This default is the T, which is the component type Any. So how do we handle this?
01:47 Now we've got errors here, and that's because we're not capturing the props here. So what we're going to do is we need to make this a generic function, right? Because we know that whatever you pass in, it's going to receive different props. We can't just make it accept this fake external component, because what if we try to load some other components?
02:06 So what's actually generic here? What do we need to make kind of dynamic in this situation? Well, it's going to be this component type. It's going to be the thing that you're passing in. And so what I want to do is I actually want to put this on a, let's call it C, for instance. And component type extends component type Any.
02:25 And now this default is going to be C here. Now what we can do is we can just grab this into C, and we can instantiate it just here. And C is going to be like this here. Now C, what you can see is that C does not satisfy the constraint component type Any,
02:43 because it's not assignable to type function component Any. That's because we need to constrain our C just here. So we need to say extends component type Any. We've seen this before actually, right? And now then, what's left to do here?
02:58 The error here that we're getting is type is not assignable to C extends memo exotic component, lazy exotic component. And we're still getting these errors down here. That's because we're not actually extracting out the component props here. And what we want to do is we need to somehow say
03:18 these props also need to contain the props of this C here, the component type. And the best way to do this is to add an intersection here and to use component props. And we're going to pass in the C. Now look at this. Now what we've got is what we're doing is we're kind of like we've got our loader here
03:40 importing this fake external components. We've got our ID string. If we don't include that, it's going to yell at us. And it's going to say ID is missing in this function. Beautiful. So let's grab that. We get autocomplete on it. Pass it in. Working. Nice. A way that you can see this because the annoying thing is with these JSX components is you can't
04:01 see actually what they're inferring as, what the type argument is being captured as when you hover over them. It just shows kind of like, oh, in fact, we are seeing this here. That's actually surprising to me. If you hover over lazy load there, you should see this function where we're basically capturing the entire fake external component there.
04:20 If I add a new prop here, if I add a name, let's say it's optional string, and then I go back here. Now we can see that name is being grabbed here too. And now props is basically capturing that entire element too. So now I can also add name onto here. So that's really, really nice. And this is the solution here.
04:40 We're using component type as a constraint to make sure that the thing we're capturing is the thing being returned from the loader, which itself is wrapped in a promise. And then we've extracted out the component props of that component type. We can take a look in here. And this is a, we've seen this kind of look before.
04:57 It's basically checking to see if it's a JSX element constructor, which component type is. And then if it is, it infers the props and returns them. So there we go.