TypeScript HOC (Higher-Order Component) and Decorators in React

Jan Hesters
6 min readDec 14, 2018

--

You are going to learn how to build a HOC for React using TypeScript. We are going to develop a Higher-Order Component that adds a badge to other components. We are also going to use them as decorators. Even though the example is for React Native, the same principles apply for all React HOCs in general.

Note: This article was first posted on my blog. I publish each article two weeks later on Medium. You might want to subscribe to my newsletter because you will get content like this earlier! 💌

Another note: This post is a follow up - targeted at intermediates - of my beginner-friendly post “How To Add a Badge to Icons in React Native”, where you can learn about HOCs in regular JavaScript.

How high? HOC-high 🔝 Photo by Erico Marcelino on Unsplash

When I created my first Higher-Order Component in TypeScript, I had to learn how to type it correctly, and I spend much time googling. In this tutorial, you will learn how to build HOCs in TypeScript, so that you can cut your learning curve. At the end of this post, you are going to see how to use them as a decorator.

1. The Example

As an example, I’m going to use an icon in a tab bar. If you want to see other use cases, check out my previous article.

The result is going to look like this 😍:

Inspired by WhatsApp 💬

Note: If you only want to see the code for the HOC skip to section 3. For the usage of decorators, check out part 4. Otherwise, code along and join me by …

2. Setting Up the App

First, we need to create an app with TypeScript. Follow the instructions here¹, but leave out the testing setup.

We need to add three packages: React Navigation (V2)², React Native Elements (preferably V1, but since it’s not out as of the writing of this post, we will use the next branch), and React Native Vector Icons. Remember to link the icons.

npm install --save react-navigation@2.18.2 https://github.com/react-native-training/react-native-elements.git\#next react-native-vector-icons && react-native link

Add the types for React Navigation.

npm install --save-dev @types/react-navigation

Create a folder called app/. Move App.tsx into it and adjust the import in index.js.

Within app/ create the folders components/, navigation/ and screens/. Create five files one for each tab (“Status”, “Calls”, “Camera”, “Chats” and “Settings”) in the screens/ directory. They should all look something like this:

Afterwards, create an index.ts file to manage the screen/ directory’s exports.

Subsequently, we are going to create the routing for our app in navigation/navigation.tsx. (We import the non-existent withBadge here. Keep reading. We are going to create it.)

Before our example app is ready, we need to use the navigator in App.tsx.

Notice how we gave our app the respective native platform’s colors for flavor.

Now our app is crashing, because we already used and imported the HOC in navigation.tsx 💥. (Relax, I’ll address the usage below.) Let’s fix this by …

3. Building the Higher-Order Component

Now we get to the meat. Have a look at the code first, then we are going to go through it together. Create a file called withBadge.tsx inside the components/ folder.

We first import the types and components that we need. Next, we are defining an interface for the options parameter of the withBadge HOC.

Then we define the HOC. We want to be able to use the HOC with a minimal syntax like this:

withBadge()(ComponentToBeBadged); // MiniBadge
withBadge(2)(ComponentToBeBadged); // Regular Badge with value 2

This is the standard syntax for HOCs. Give the first function the injected parameters and pass the second function the component that should be modified.

Therefore the first parameter for our HOC has to be the value parameter that models React Native Elements value in the BadgeProps. If you incorrectly used the beta version of RNE, this will throw an error, because only since the actual version one value can be of type ReactNode. We made this parameter optional so that we can also use it for MiniBadges.

The second parameter of the HOC is just an object called options. Its type is a conjunction of our WithBadgeProps and the BadgeProps types. The reason for this is that we want to be able to control the badge with the HOC, too. We give it an empty object as its default value. It allows us to use the HOC with the following API:

withBadge(1, { status: "success", top: 5 })(ComponentToBeBaded)

Consequently, we define the WrappedComponent as the return value of the HOC’s first function. Since it is a component, it is of course of type ComponentType. We use generics and extends to declare an object as the WrappedComponent’s props’ default. (Note: For decorators, we have to change that - see section four.)

Lastly, we return a regular React class and pass the component the P generic, so that type checking works correctly. The rest is just basic React code that takes care of proper styling and props passing.

The writing and typing of Higher-Order Components in TypeScript are as easy as that. It takes a little time to get it right, but if you’ve done it once it’s easy to repeat 😊.

If you are into bleeding edge syntax, you might also want to learn how to use …

4. Decorators 🎖

Decorators are a proposal for JavaScript that we might get in the coming years. Other programming languages already have them.

To use decorators, we have to make small changes to our typescript processing and the typing of our component. Start by installing the babel plugin for decorators.

npm install --save-dev @babel/plugin-proposal-decorators

We need to tell babel to use the plugin by adding a plugins key to our .babelrc file.

Additionally, we have to activate decorators in our tsconfig.json to get our linter to be okay with us using them.

That’s it for the configuration. We still have to make some changes to our HOC’s typing. Decorators are internally typed like this:

Therefore we can’t use a return type of ComponentType. Otherwise, your linter will yell at you and your compiler, depending on your settings, might not compile. To compensate for this, change the return type of your first arrow function to void.

): <P extends object>(WrappedComponent: ComponentType<P>) => void {

Now you can use the decorator like this³:

Lovely clean code 👾

Enjoy your experimental features 🚀.

If you use Higher-Order Components in TypeScript, you might want to clap a bunch of times for this article (or share it with a friend), because it would help me out a lot.
Thank you 🤗

My name is Jan Hesters. I’m a Full Stack Developer specialising in JavaScript, TypeScript and Python. I’m also a studied physicist and psychology-enthusiast.

NOTE: This article was first posted on my blog. I publish each article two weeks later on Medium. You might want to subscribe to my newsletter because you will get content like this earlier! 💌

Footnotes

  1. If you like the guide to create a TypeScript app that I linked, please up-vote this PR, because you can help to get it posted on the React Native blog. It will make following the steps to create a new RN app with TypeScript easy for everyone.
  2. As of writing of this post React Navigation V3 does not have types, yet.
  3. If you use void as a return type you can’t use it in the classic way. The API is just broken right now, since it is still an experimental feature. If I’m wrong with this assumption, please reach out to me on Twitter and correct me.

--

--