Why you should use generic types in TypeScript, a simple example

10 Jan 2020
title

Edit: This article was originally posted on my Medium page, if you liked it leave a clap! You can join the discussion on Reddit too.

TypeScript has a steep learning curve, at least for me, for a front-end developer used to write without types sometimes I struggled to change my way of thinking to my code. When people used syntax like in StackOverflow answers, I have always been fascinated yet scared so I decided to put down a simple example to help people like me to become a “cool guy”.

First of all, let’s say we want to have a TypeScript function which receives an array of objects and filters this array by a single property value:

1// Define a book type
2type Book = {
3  title: string
4  genre: string
5  publicationYear: number
6}
7
8// Define a list of types
9const listOfBooks: Book[] = [
10  { title: "Dragon Of The Titans", genre: "fantasy", publicationYear: 1992 },
11  { title: "Queen Of Spring", genre: "drama", publicationYear: 2005 },
12  { title: "Wolves Of The King", genre: "fantasy", publicationYear: 1988 },
13]
14
15// Use a common function to retrieve books
16const filterArrayByValue = (
17  items: Book[],
18  propertyName: string,
19  valueToFilter: string
20): Book[] => {
21  return items.filter((item) => item[propertyName] === valueToFilter)
22}
23
24// Call the function
25// The result is an array with two books: "Dragon Of The Titans" and  "Wolves Of The King"
26console.log(filterArrayByValue(listOfBooks, "genre", "fantasy"))

So now we have a function that filters an array of** Book **and returns a filtered array of **Book **based on the property and the value we pass in the second and third parameters.

Let’s say we have another type called Car and we want to use the same function to filter a list of Car[], right now we will trigger an error.

Our function this way is not reusable, what we can do is use a generic type T instead of a defined type like Book:

1// Add a new generic type T
2const filterArrayByValue = <T>(
3  items: T[],
4  propertyName: string,
5  valueToFilter: string
6): T[] => {
7  return items.filter((item) => item[propertyName] === valueToFilter)
8}

We now have a reusable function and we can use it with any entity that meets our type condition.

We can still write a better function, in our case we want the propertyName to be the name of a property contained in the type of T, plus we want the value to filter to be a partial value of the entity T. We can introduce another generic type which extends T, called P to tell TypeScript we want to use a property of T as the second parameter:

1// Add a new generic type P
2const filterArrayByValue = <T, P extends keyof T>(
3  items: T[],
4  propertyName: P,
5  valueToFilter: string
6): T[] => {
7  return items.filter((item) => item[propertyName] === valueToFilter)
8}

Last we can now define the valueToFilter parameter as following:

valueToFilter: T[P]

in our case valueToFilter is a partial value of T, so we can also define it as follows:

valueToFilter: Partial<T>

Here is our final function:

1// Let's define our valueToFilter type
2const filterArrayByValue = <T, P extends keyof T>(
3  items: T[],
4  propertyName: P,
5  valueToFilter: T[P] //Partial<T>
6): T[] => {
7  return items.filter((item) => item[propertyName] === valueToFilter)
8}

Here is our final code:

1type Book = {
2  title: string
3  genre: string
4  publicationYear: number
5}
6
7type Car = {
8  modelName: string
9  type: string
10  price: number
11}
12
13const listOfBooks: Book[] = [
14  { title: "Dragon Of The Titans", genre: "fantasy", publicationYear: 1992 },
15  { title: "Queen Of Spring", genre: "drama", publicationYear: 2005 },
16  { title: "Wolves Of The King", genre: "fantasy", publicationYear: 1988 },
17]
18
19const listOfCars: Car[] = [
20  { modelName: "Yellow Car", type: "coupe", price: 20000 },
21  { modelName: "Blue Car", type: "SUV", price: 45000 },
22  { modelName: "Green Car", type: "coupe", price: 18000 },
23]
24
25const filterArrayByValue = <T, P extends keyof T>(
26  items: T[],
27  propertyName: P,
28  valueToFilter: T[P] //Partial<T>
29): T[] => {
30  return items.filter((item) => item[propertyName] === valueToFilter)
31}
32
33console.log(filterArrayByValue(listOfBooks, "genre", "fantasy"))
34console.log(filterArrayByValue(listOfCars, "type", "SUV"))

That’s it! Thank you if you arrived so far, understanding the concepts above gave me a great understanding of TS world, I hope you found it helpful!

Get The Best Of All Hands Delivered To Your Inbox

Newsletter subscription is temprarily disabled 🙂