Some React patterns are trickier to implement in TypeScript than others and require knowing the right approach and utilizing the right TypeScript features. The higher-order component is one such pattern.
In this post, we will explore creating a higher-order component from scratch and dynamically calculating its prop requirements using generics and utility types.
What is a Higher Order Component?
Higher-Order Component (HOC) is a function that takes another component and injects additional properties into it. Think of it as wrapping your component with an extra layer that gives it more functionality.
The purpose of HOCs is often to reuse common logic between components and separate logical and presentational layers. With the introduction of hooks, HOCs took a back seat for the most part. Although you see them less, HOCs are still relevant and have certain advantages over hooks, for example a better compatibility with the class-based components.
A well-known example of a HOC is the
withRouter
function from the react-router
library.
Setting up the HOC
Let’s start by scaffolding our HOC first. We will create a simple HOC that adds timer functionality to our components. Our first iteration will not have strict typing, but we will tighten it up as we progress.
Here’s what the first version of our higher-order component looks like:
At this point, our HOC is not very typesafe since we set any
for the target component’s type. There’s no type checking to make sure the component we pass accepts the props we’re trying to inject.
Now let’s start tightening the type checking.
Adding Generics
Let’s make it so that our HOC only accepts React components and that it expects the same props as the target component. We can use TypeScript generics to enforce these restraints:
ComponentType is a special type React provides for working with components in TypeScript.
Also, notice our use of generics. We used the T
type in several places:
- We’re setting our parameter type to
ComponentType<T>
. Now, within the scope of this function,T
denotes the props type of the target component. - We’re also setting the
hocProps
type toT
to enforce that our HOC component receives the same props as the target.
Thanks to generics, TypeScript can dynamically calculate all of the props our original component accepts and enforce the same restriction for the HOC. You can learn more about generics from TypeScript docs .
Using utility types
Type checking in our HOC is looking good so far. Here’s how we would use it:
But there’s still one more issue we need to fix. When you put our HOC to use, you get an error from TypeScript:
Earlier, I stated that the HOC should expect the same exact props T
as the target component, well that’s not entirely true. Our HOC shouldn’t expect props counter
, startTimer
, and endTimer
because it’s the higher-order component’s job to inject these props in the first place.
We can get around this issue by using the
Omit utility type
. Using Omit, we can tell the HOC to expect all of the props our target component expects, except for the ones it injects:
We made two more changes to our code:
- Now our HOC accepts props of type
hocProps: Omit<T, "count" | "startTimer" | "endTimer">
. UsingOmit
, we created a new type that expects all of the same props asT
except forcount
,startTimer
, andendTimer
. - Since
Omit
creates a new type, we had to use a workaround{...(hocProps as T)}
to let TypeScript know that we expect thehocProps
to be almost identical toT
except for the props we omitted.
Now our higher-order component is ready to be used!
Conclusion
In this post, we covered the proper way to write higher-order components in TypeScript. We made our HOC both flexible and safely typed using TypeScript’s generics and utility types. Take a look at my post to learn more about other TypeScript utility types useful for React .
Here’s a link to the code sandbox for this tutorial. Thank you for reading!
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.
Happy coding!