Typescript, in spite of all its advantages, can be hard to swallow at first.
Readability of Typescript code, is the main reason in my opinion, for developers being reluctant to give it a real shot.
Let’s take a look at this piece of code:
Now, what about this?
This code is less pleasant, to say the least. It gives us reusable types to work with. I extracted it from one of the projects I have been working on, but such code is a common thing to find in other projects as well.
The purpose of this post is to give you the tools, to be able to read and write such code yourself. We will do it using real-life examples from the front-end world, of building highly reusable components using Typescript with React.
We will explore 3 subjects — Generics, Utility types, and Event handlers.
Let’s take a look at the following example — A reusable table component.
- Our table component can be set with any kind of data for the columns and rows.
- The data in the table can be sorted by clicking on the column’s title.
- A single row can be selected when the user clicks on it.
In order to achieve this behavior we will use this simple code:
Now, a developer who wishes to use our reusable component to display a list of candidates will use it this way:
Code looks good, but what about type safety? In order to achieve it, we will rewrite this code with Typescript and use Generics.
The interesting parts of this code are lines 6, 7, and 20.
Line 7 is the easy one, we are telling Typescript that the Array is of type
Candidate. Since Array is a generic type, we are using this syntax:
Line 20 — We need
Table to know what type of rows it contains, so we pass it the
Candidate type –
Table<Candidate>. In the next code snippet, you will see how
Table is using this new
Line 6 — That one is indeed weird. What we try to achieve here, is to verify the items of columns array, can only be the keys of the row type — Candidate. For example, if we make a mistake and instead of passing
firstName we pass
firsName, Typescript will alert us:
So when are we actually using these types and benefit from them?
Take a look at our callback functions:
In order to access firstName, lastName, and age, in the function’s signatures, we declared
We later on pass these functions to our component:
If we try to pass an invalid signature, for example, a
Product instead of a
Candidate, then Typescript will alert us:
Ok, so we modified our Table’s usage code, now let’s see how we rewrite the
Table to work with it:
As our Table component provides the following props:
rows, cols, onRowClick, and onSortClick, we created an interface for them.
This interface can get whatever
RowDataType we want. It could be a Candidate, a Product, etc’.
Well, I lied a bit, not whatever we want, but at least one that contains an
id:string so we can manage it better internally in our Table component.
extends syntax enforces it:
The generic interface saves us from repeating ourselves again and again for each different type:
Going back to the rest of Table’s code:
Line 1 —
Table<RowDataType> enables us to use
RowDataType anywhere we want through the function. You can do the same exercise we did before, remove the generic on top, and replace all its instances in the function’s body with
NOTE TO MENTION
In most code, you will encounter a short version of naming like T, K, V, etc’.
I choose to write the full meaningful name –
<RowDataType> for better clarity of the examples. But could of-course do it the shorter way:
Next part — Utility Types
This part talks about
Partial, Pick, and Omit which are part of the utility types that Typescript supply. These types help us make types’ transformations. The official typescript docs contain a short and solid doc about it.
What we will do here, is give real-life examples of such usages.
Let’s say that you want to create a reusable Modal component, to be used across your different applications.
Consider your Modal is based on react-modal to save you the time of handling the gray-out area, z-index layers, accessibility, and more.
Because we want all our apps to feel the same, our Modal will have a strict design — black border, white background, and dynamic width and height.
In order to achieve it, we will allow our user to pass all react-modal props, besides the ones related to design.
Our code would like somewhat like this (longer and more precise working example can be found at the end of this post):
Line4 — all the props that we wish to Omit
Line5 — creates a new type — all react-modal props, except for the design ones.
Line6 — creates a new type — the reduced react-modal props + width and height props.
Line17 — notice how we are using the ES6 rest parameter syntax to forward the rest of the props to react-modal. Thanks to
Omit, we can be sure our user won’t pass unwanted design props like
className for example.
We have seen a real-life example of
Omit usage. What about
Pick is doing the opposite, it gives us back only the props that we wish to keep. So if we would like to get only a small portion of the props, it would make more sense to use Pick:
Lines 1–3: Demonstrates a one-liner for the types which is more common. If it’s not clear at first read, I suggest you split it into multiple lines like in the previous example.
With this type, the consumer of this component will be able to pass only 4 props — isOpen, style, width, and height.
Another useful utility type is
Partial, which takes a type and make all its props optional.
In react-modal props, only
isOpen is mandatory. If we decide that in our modal it is not mandatory but uses a default value when not supplied, we can still reuse react-modal types using
This is almost the same code as in the first modal example, but notice how we use
Partial in line5 and give a default value
isOpen=false in line9.
Last subject — Event handlers
Another piece of code that looks weird at first is where we use existing types for event handlers.
Let’s take a look at this auto-complete example:
As you can see from the interface, our component expects to get a
className for design purposes,
options to display for the auto-completion, the selected
value and in lines 5–11 callback functions to execute as event handlers.
A developer would use our component somewhat like this:
Lines 3–4 —
handleOnChange is setting the state with the selected value, which is
In order to work safely with
ewe had to know it’s type and that is what this
React.ChangeEventHandler<HTMLInputElement> gives us.
If we had to write this type ourselves, we would use HTMLInputElement type and augment it with
target. It is not a fun task, and luckily React does it for us, for all kind of event handlers.
So, getting familiar with this useful types for event handlers is important. If you like to improve your Typescript skills even better, take a look here at how React implements it.
The code examples in the post were reduced for better clarity of the idea I tried to convey. Longer and more precise versions of all code examples can be found here.
I really hope you enjoyed reading this post, and gained a thing or two from it, at least I did.
Feel free to leave a comment below with any questions or feedback you might have.