Back

How to improve React component type-safety with Typescript generics?

How to improve React component type-safety with Typescript generics?

Henrikas Jasiūnas

Senior Software Engineer

2023-05-29


While a big part of the front-end development community has embraced Typescript, not many developers take full advantage of the benefits that it provides. One of such underutilized features, without a doubt, is generics. The concept of generic types isn’t an easy one to wrap your head around, but once you’ve understood its power, you’ll be able to take your components to the next level by making them more flexible, reusable, and maintainable. In this blog post, we’ll take a close look at a specific example of how to improve the type-safety of a React component by using Typescript generics.

The problem

To demonstrate the problem, let’s make a custom select dropdown. Usually, components of this kind contain some extra logic and/or styling, but in our case, it simply wraps a native HTML select element:

As you might expect, it has value, options,and onSelect props, and we are using a string type for the value. Let’s include this component into our App.tsx:

Now we need some data to pass to our component. For the options, we’ll take values of an enum, and the selected option value will be set in the component state:

And finally, when the onChange event is emitted, we want to set the selected option as the value from the event emitter. But as we do that, we run into a Typescript problem:

Even though all the options that we pass to our SelectDropdown component have the same value type as selectedPlatform, we cannot assign a value to it from onChange because it’s typed as a string:

The solution

Now let’s take a look at how a developer would likely deal with this kind of issue. The easiest and most straightforward thing to do here is, of course, to simply use any instead of string in the onChange:

“Voila, the job is done – not a single Typescript error in sight!“.

However, one of the first things everyone should learn about Typescript is that using any is a risky practice that should be reserved for those rare cases when no other option is available. Let’s try to find a better way.

Another common approach to dealing with such issues is by using type casting:

The error is fixed! However, while casting is not as bad as any, it is still not type-safe and can lead to errors. Consider the following example:

The option value type has changed from Platform to Status, and Typescript is not displaying any errors. However, if you take a closer look at the onChange handler, you’ll notice that we’re setting selectedPlatform to a value of a mismatching type. While spotting a mistake of this kind is quite easy in our simplified example, this might not be the case in more complex situations.

The generic solution

Now that we understand the problem, let’s explore how generics can help us out. If you are unfamiliar with the concept of generics, you can think of it as a tool that allows you to create a piece of code that works with a variety of types instead of just one exact type. Kind of like any but a lot more clever. To learn more, see the official Typescript documentation.

So, in order to transform the SelectDropdown component into a generic one, first let’s change the types in the Props interface:

The important thing here is that the type of value in the Props interface is now generic T instead of a string. However, there are now a bunch of Typescript problems such as this:

It makes sense because, according to our Props interface, the value now can be any type, but the native select element only accepts values of certain types. We can easily fix this by restricting the type with the extends keyword:

With this restriction, you can think of T as a string-like, which is, nonetheless, generic in the sense that the exact type of T is unknown in the context of this component. In general, it’s a good idea to be restrictive for the sake of avoiding unexpected outcomes.

However, as you can see in the screenshot above, there’s still one Typescript error remaining. The problem here is that the native select element’s onChange event emitter is emitting a value with a type string that cannot be assigned to our generic T. To fix this, we can use the following workaround, iterating through the options array to find the option with the correct type:

Now that we have our generic component, let’s see how it’s used. You might expect needing to specify the concrete type when declaring the component, like this:

While this is a valid syntax, in most cases it is actually not necessary to explicitly type it — Typescript is smart enough to infer the type from the props that were passed to the component. So, we can simply omit the explicit type and use it just like a regular component:

And that’s it! We have no Typescript errors and our component is actually type-safe. As you can see, if we hover over the onChange prop, it shows the correct type:

And if we change the type of option values, we get a compiler error:

 

 

Conclusion

In this article, we looked at a fairly common React component type issue that results in overuse of type casting or other unsafe workarounds that should be avoided. We then explored how generic types can be utilized to solve such issues, thus improving type-safety.

Links:

Github repository of the example code.