Section 2: Excluding Props
This is covered in passing in Section 1 but we focus on it here as it is such a common issue. HOCs often inject props to premade components. The problem we want to solve is having the HOC-wrapped-component exposing a type that reflects the reduced surface area of props - without manually retyping the HOC every time. This involves some generics, fortunately with some helper utilities.
Say we have a component:
And we have a withOwner HOC that injects the owner:
We want to type withOwner such that it will pass through the types of any component like Dog, into the type of OwnedDog, minus the owner property it injects:
So how do we type withOwner?
- We get the types of the component:
keyof T - We
Excludethe property we want to mask:Exclude<keyof T, 'owner'>, this leaves you with a list of names of properties you want on the wrapped component e.g.name - (optional) Use intersection types if you have more to exclude:
Exclude<keyof T, 'owner' | 'otherprop' | 'moreprop'> - Names of properties aren't quite the same as properties themselves, which also have an associated type. So we use this generated list of names to
Pickfrom the original props:Pick<keyof T, Exclude<keyof T, 'owner'>>, this leaves you with the new, filtered props, e.g.{ name: string } - (optional) Instead of writing this manually each time, we could use this utility:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> - Now we write the HOC as a generic function:
Note that we need to do a type coercion here.
This is because TypeScript does not know that merging Omit<T, "owner"> and {owner: "whatever"} is the same as T.
See this GitHub issue for more.
Learn More
We will need to extract lessons from here in future but here they are: