Building React Components with TypeScript Generics

Give your React Props a Generic Type

Michael Paravano
6 min readNov 27, 2020

Strong typing; If you’re a React JavaScript developer who has turned to working in TypeScript, you’ve no doubt felt the cut of that double-edged sword.

On one hand, the strong typing that TypeScript provides often catches things that we might have otherwise overlooked and prevents disaster before it happens. Thank you TypeScript!

On the other hand, it also locks in our React props to a given set of types when we may not know what type the consumer will want to pass in. TypeScript, why are you tryin’ to control me?!

Imagine that we are tasked to build a reusable table component which will have a data prop that is an array of the items which will make up the rows and columns.

We have no idea what the consumer will pass in so…. what the heck do we do now?

Don’t worry! The Great T will solve everything!

Why bother? Why not just use “any”?

Well, that certainly is one way to live, but the question to ask yourself if you do this is, “Why am I bothering to use TypeScript at all?” In addition to that, if your component has any events that send data out, your consumer will have to recast your any to their desired type.

“That’s not a big deal, bro. I think I’m going to stick with any.” — You (possibly)

It may not be a big deal if you have to do it only once or twice, but if you get in the habit of building components that way, it’ll add up to consumer frustration rather quickly. So let’s be customer focused!

The Scenario

I mentioned at the beginning that a table is a classic scenario for generics but, in an effort to keep this article more concise, let’s go with something else. It’s a little more contrived, but bear with me.

Let’s say that we are tasked to build an EmployeeInfo component which will display an employee’s avatar, name, and job title.

We could easily create a component that could look like this:

<EmployeeInfo
firstName="John"
lastName="Smith"
title="Worker Guy"
image="http://somesite.com/john-smith.jpg" />

But let’s say there is one more requirement, when our component is clicked, we want all of John Smith’s available data to be returned. On top of that, this component will be used across teams with different contexts where the concept of an employee might go beyond this or be quite differently shaped.

Our goal will be to be able to pass in the full employee info into our component like this:

<EmployeeInfo
employee={info}
onClick={employee
=> console.log(employee)} />

The First Attempt

We’ll first start by defining what that info looks like:

Here is our first attempt at building our React component in TypeScript:

Notice that we’ve defined our props as an interface calledIProps which defines the employee prop as type IEmployeeInfo.

Here is our app that is consuming this component:

…which will output this:

Everything is strongly typed and assuming everyone is using some form of IEmployeeInfo. Just to prove that, we can extend IEmployeeInfo into a different kind of employee:

And then we update our consuming app to use it instead of IEmployeeInfo:

Which (no surprise) will output this:

This is because an IEmployee “is” an IEmployeeInfo. However, the downside is that our onClick param is viewed as an IEmployeeInfo by our code editor:

If the consumer doesn’t need any of the other details of IEmployee, then this is a fine solution, but we can do better.

Let’s Go Full Generic Mode!

Now, what happens when a different team has a completely different shape for their employee data? This is where the power of the TypeScript generics come into play.

Let’s say this other team has a CustomEmployee type that looks like this:

Every key is different from IEmployee.

Let’s start by modifying our component’s IProps interface to accept a generic type:

We’ve included the angle brackets with the letter T to indicate an unknown type. By the way, we don’t have to use T. We can use any designation we want, but T is customary.

Notice that the type T has been used for the employee prop type on line #2 and for the employee type in the parameter for the onClick prop type defined on line #3. Whatever type we define forT, it will be used in all of those places.

Next, we’re going to change part of our component’s function signature (ignore the added keys prop for now):

We’ve also added T to our component and we’ve given it a default of IEmployeeInfo if the consumer doesn’t pass us anything for the type. We’re then handing T over to the IProps interface so the types of our props can be fully defined.

Please note, this is a functional component. If you have a class-based component, the syntax will obviously be different. We’ll get to what you need to do for that in a bit.

We’ve added a new prop called keys which has the type Keys that is defined like this:

You’ll notice that Keys has the same keys as IEmployeeInfo. Our plan is to use ourkeys prop to map T to an IEmployeeInfo.

You’ll probably also note that I’ve added a convert function at the bottom. This function (also generic) will convert a given object to an IEmployeeInfo such that we can easily reference the members with type security throughout (see lines #20, 24, 27). This is how we’ll accomplish the mapping.

Let’s update our App.tsx:

We’re now using a CustomEmployee type (lines #6–14) and we’ve defined our keys map (lines #16–21).

Now, this is where the magic happens.

Take a look at how we’re consuming the component on line #25:

<EmployeeInfo<CustomEmployee>

We have passed in theCustomEmployee type as our T and that’s all there is to it. If we hover over the employee parameter in our onClick callback on line #28, you’ll see that it has correctly identified our type as CustomEmployee:

CustomEmployee type correctly identified

If we run our project, we see:

BAM! We did it!

Awesome, but I’ve got a class-based component. What do I do?

Your changes will be similar:

The key difference to note is line #10. You are similarly defining T on the component with a default of IEmployeeInfo and passing it to IProps.

To Sum Up

Functional Component Syntax

function EmployeeInfo<T = IEmployeeInfo>(props: IProps<T>): JSX.Element {/* Component code goes here. */}

Or if you prefer ES6 const syntax…

const EmployeeInfo = <T extends {} = IEmployeeInfo>(props: IProps<T>) : JSX.Element => {/* Component code goes here. */};

The one difference to note here is that we will have to use the extends = {} syntax with ES6 in order for our IDE to know that this is intended to be a generic type and not the opening of a JSX tag.

In our case, we can omit the {} = and simply extend IEmployeeInfo, however, I left it in because you will need to include it when you have no idea what your type will be.

Personally, I find the extends {} to feel a bit hacky which is why I generally favor the traditional, but you are free to use whichever you prefer.

Class-based Component Syntax

class EmployeeInfo<T = IEmployeeInfo> extends React.Component<IProps<T>> {/* Component code goes here */}

You’ll now be able to provide your consumers with strongly typed React components that are flexible enough to have the types that they want.

--

--

Michael Paravano

Senior Front-end Engineer specializing in all things front-end software development.