Advanced Patterns 11 exercises
solution

Implementing a Generic Higher Order Component

The first thing we need to do is update withRouter to be generic.

Making withRouter Generic

We'll start by capturing the props passed to withRouter`` as TProps`:


export const withRouter = <TProps,>(Component: any) => {
...
};

Next, we need to define the Component

Loading solution

Transcript

00:00 OK, let's give this a go. So, with router, what's the thing that we need to capture here on the with router? We really need to capture the props, really. Because it's the props are the thing that are going to be dynamic. If we were to add another prop on here, like name, string or whatever, then we need to make sure that this is added to the wrapped component props type.

00:19 So let's add this on the outer function here. Of course, we're in a TSX file, so it's not going to work very well here. And let's just say tprops, I'm just going to add the comma for now. Now, components here, what type is the component going to be? Let's just hash this out for now.

00:36 Let's just say it's going to be a function which returns react.reactNode. And these props are going to be tprops here. So now then, this is all looking good. And let's just hover over this. So we've got with router. You can see that the props are properly being captured here, which is really, really nice. So now router.id and name.

00:54 And let me just remove name just so we're back with the original example. So we've now got router.id being understood that these are the props that are being passed in. But then, if we hover over this, we can see props.any is still there. And that's because of this any just up there. So let's take a look at this.

01:11 And what we know is that these are going to be related somehow to the tprops. And we can see then that this is working okay now. And in fact, our wrapped component is understanding a little bit about the thing that's being passed in.

01:27 If we add name to this and we go back down here, then name is going to be added onto here too. So name and router, this is all working pretty well. Except though that we actually want to omit a certain prop here. We actually want to omit the router prop.

01:42 So we can use that by using the omit type helper. Because omit, if we just say omit tprops router, now it understands that, okay, these props, they should never contain router on them. And now actually outside our components, it's actually working pretty nicely.

02:00 We still got some errors inside though. So what's this? Omit tprops router could be instantiated with an arbitrary type that could be related to omit t... Extremely terrifying.

02:12 And I'm tempted in these sort of situations, because everything is like working outside of the component, is to use an any in a certain situation. The thing that we want to get across here is that we are actually passing all of the props in. I wonder if I rearrange this in a certain way.

02:28 So if I say const new props equals props and then router. If I get and I actually type this as tprops, will this work? No. So this isn't assignable to it for exactly the same reason. So it could be instantiated with an arbitrary type.

02:46 And I'm most tempted actually, because I know that I'm passing in everything there, is I can basically say as tprops here. Now, though this has a bit more of an overhead, we've actually ended up changing the runtime code there, which I'm not very happy with. But I just want to check if this works. Of course, this doesn't work either. Type tprops is not assignable to intrinsic...

03:05 Oh, dear, oh, dear. So let me go back. I think the best solution here is basically to essentially do this, which is what I've got in the solution is say props as tprops and then router, router. This was the solution that I came up with. We would probably need if this was production code, we'd need a comment here.

03:22 But because we're inside extremely generic code, I really don't want to hit that error more than I need to. And actually, it's all seems to be working outside of the generic signature, which is really good. Now, let's take a look at display name here. So component.displayname does not exist on this function type.

03:39 Now, this function type, what does this look similar to? What do we know it like has exactly the signature that we've seen before? It's react.fc. And react.fc, it has the attribute of display name on it.

03:52 So if we look at tprops again, and I take a look at this function component, it's got our display name right there. And I think too, it's not only FCs that we want to be able to pass into here. It's also component type, right?

04:07 So component type, if we take a look, this is a function component or a component class, which also has a display name on it. This means that we can pass in this to our component too. So we're doing pretty well here. We've got our unwrapped components sort of working nicely.

04:25 And that's, you know, still needs a router here. We haven't changed anything there. And our wrapped component is now corresponding to the actual props that we want. And ID is like requiring the correct property too. So this, I think, is the best way to, this is, I think, the simplest solution to building a HOC with React and TypeScript.