Getting to Know TypeScript Generics
Tags: typescript, javascript, react
Published: 2023-05-18 16:47:24

---
title: Getting to Know TypeScript Generics
published: true
date: 2023-05-15 19:00:00 UTC
tags: TypeScript,JavaScript,React
canonical_url: https://devblogs.microsoft.com/cse/2023/05/17/getting-to-know-typescript-generics/
---

## Getting to Know TypeScript Generics

## Introduction

DRY. Do Not Repeat Yourself. DRY is an acronym that became synonymous with engineering fundamentals. Using DRY, we ensure that we don’t find ourselves with the same lines of code repeated over and over in a repository; indeed when the inevitable need arises to add a single tweak, we come across the predicament of having to go back to each-and-every-single-repeated piece of code to update each individually.

Repeating code was a challenge we quickly came to face during an engagement with a customer where we needed to implement card component selection functionality using [React Hooks](https://react.dev/reference/react). The reason we had to repeat ourselves, was that in multiple instances we needed to implement the card selection state to handle multiple data types (`string`, `number`, etc.). While in the midst of the `ctrl+c, ctrl+p` routine, we decided to refactor our components to be more maintainable and properly “reusable” to embrace React’s full capability of custom hooks, while still allowing us to keep our type safety in the realm of `TypeScript.`

Thus, we had the opportunity to leverage `TypeScript Generics` as a way to keep DRY, increase maintainability, and keep in the recommended pattern of React Reusable components.

Below, this post will take you through examples of where `TypeScript Generics` can help DRY up your code, while demonstrating both a generic TypeScript function sample and a React Custom Hook example that follows a closer approach that was used during our customer engagement.

## TypeScript Generic Overview

A TypeScript generic is a way of creating reusable components that can work with different types of data rather than one. It allows you to specify a placeholder type that represents the actual type that will be used when the code is executed. For example, you can define an array as `Array<T>` where T is any type you want.

## TLDR Sample

If you prefer to play around with an example, refer to this [Code Sandbox React Hook Example](https://codesandbox.io/p/sandbox/hbl9nl?file=%2FREADME.md)

## Problem Example

Let’s say that we have two functions that virtually do the same thing, however the parameter may have multiple types. Let’s say that we have a business, and we sell **bikes** and **tennis racquets**. Now, both items are different, albeit they both have a `price` tag attached to them.

So we have a bike `type`:

```ts
type Bike = {
  price: number;
  name: string;
  productType: number; //enum 1 - Electric, 2 - Mechanical
  tires: string;
  weight: number;
  length: number;
  height: number;
}
```

and a tennis racquet `type`:

```ts
type TennisRacquet = {
  name: string;
  price: number;
  brand: number; // enum 1 - Babolat, 2 - Head, 3- Wilson
  weight: number;
  gripSize: number;
  size: number;
  racquetStrings: []string;
}
```

Both `Bike` and `TennisRacquet` are pretty different in types, however they have a `price` in common. To find the average cost of our `TennisRacquets` and `Bikes`, we have 2 functions:

```ts
// Average Bike Cost
const averageBikeCost: number = (bikes: Bike[]) => {
    const sum: number = bikes.map(bike => bike.price).reduce((val, sum) => val + sum, 0);
    const average = sum / bikes.length;
    return average
}

// Average Tennis Racquet Cost
const averageTennisRacquetCost: number = (tennisRacquets: TennisRacquet[]) => {
    const sum: number = tennisRacquets.map(tennisRacquet => tennisRacquet.price).reduce((val, sum) => val + sum, 0);
    const average = sum / tennisRacquets.length;
    return average
}
```

Both these functions look fairly identical, except for the type. We could combine these two functions, without much difficulty since both just use the `price`.

```ts
const averageProductCost: number = (products: TennisRacquet[] | Bike[]) => {
    const sum: number = products.map(product => product.price).reduce((val, sum) => val + sum, 0);
    const average = sum / products.length;
    return average
}
```

However, to introduce a bit more complex functionality to get a mapping of the average of each “type” of racquet and bike, E.g. The average cost of all the electric bikes, and the average cost of all Babolat racquets.

If we want to wrap this in one function, countSumMap, we will have to type narrow as shown below.

Now Imagine if we want to extend this by another type if we add to our inventory of products (E.g., type `Shoes`). We would be increasing this example quite a bit, and it will feel very repetitive

```ts
const countSumMap: Map<string, { count: number; sum: number }> = (prods: Tennis[] | Bike[]) => {
    const newMap = new Map<string, { count: number; sum: number }>();
    if ("tires" in prods[0]) {
        prods.map(prod => {
          if (newMap.has(prod.productType)){
              const { count, sum } = newMap.get(prod.productType)
              newMap.set(prod.productType, { count: count + 1, sum: prod.price + sum });
          } else {
              newMap.set(prod.productType, { count: 1, sum: prod.price });
          }
        })
    } else if ("handleSize" in prods[0]) {
        prods.map(prod => {
            if (newMap.has(prod.racquetBrand)) {
                const { count, sum } = newMap.get(prod.racquetBrand)
                newMap.set(prod.racquetBrand, { count: count + 1, sum: prod.price + sum });
            } else {
                newMap.set(prod.racquetBrand, { count: 1, sum: prod.price });
            }
        })
    } else if ("shoeSize" in prods[0]) {
        // ... conditional logic for type Shoe 
    }
    return newMap
}

const tennisRacquetsCountPriceMap = countSumMap(tennisRacquets);
// Map(1) { 'Babolat' => { count: 1, sum: 150 } }
// ... extra logic to find average / mean price
const bikeCountPriceMap = countSumMap(bikes)
// Map(1) { 'Electric' => { count: 2, sum: 1500 } }
// ... extra logic to find average / mean price
```

This is where Typescript Generics come in handy!

## Using Generics

We can use a type Generic `<T>` to help us reduce this repetitive code.

Notice the code shown below, this is much more concise, and also reusable.`countSumMap` can be used for any object with a `price`. `keyName` is given to allow us to define how we organize our summation and count.

```ts
const countSumMap: Map<string, { count: number; sum: number }> = <T>(prods: T[], keyName: string) => {
    if (!keyName) return new Map();

    const newMap = new Map<string, { count: number; sum: number }>();
        prods.map(prod => {
            if (newMap.has(prod[keyName])){
                const { count, sum } = newMap.get(prod[keyName])
                newMap.set(prod[keyName], { count: count + 1, sum: prod.price + sum });
            } else {
                newMap.set(prod[keyName], { count: 1, sum: prod.price });
            }
        })
    return newMap
}
// console.log(countSumMap(bikes, "productType"))
// Map(2) {
// 'Electric' => { count: 2, sum: 1500 },
// 'Mechanical' => { count: 1, sum: 3000 }
// }
```

`countSumMap` will infer `<T>`, as whatever type we pass as our parameter to `prods`. If our `countSumMap` takes an argument of `Bike[]`, then it will be able to handle the logic while inferring `Bike` as the type used in the function body. The same goes with `TennisRacquet`.

## Another Example – React Custom Hook

> NOTE: For a Full Working Example, See this [Code Sandbox Demo](https://codesandbox.io/p/sandbox/hbl9nl?file=%2FREADME.md)

For this example, assume that we are attempting to set up an item selection that allows us to define a `multiSelect` mode, so we can either add a single item as our `activeItem`, or we can select `multiple`.

- Think of holding `shift` to select multiple.

The first step will be to create a [custom hook](https://beta.reactjs.org/learn/reusing-logic-with-custom-hooks) that will allow us to create a React hook that can receive any `type` in a `Set`, and allow items to be stored and modified in that `Set`. Our custom hook will also receive `multiSelect` boolean to define if we want to do a single active item, or multi select.

```tsx
import { useState } from "react";

interface ItemState<T> {
    selectedItems: Set<T>;
}
interface ItemActions<T> {
    handleItemSelect: (item: T, multiSelectMode: boolean) => void;
}
export const useSelect = <T>(items: Set<T>): [ItemState<T>, ItemActions<T>] => {
    const [selectedItems, setSelectedItems] = useState<typeof items>(items);

    const handleItemSelect = (
        item: T,
        multiSelectMode: boolean = false
    ): void => {
        const copiedSet = new Set<T>(selectedItems);
        if (copiedSet.has(item)) {
            copiedSet.delete(item);
        } else {
            if (!multiSelectMode) {
                const newSet = new Set<T>();
                newSet.add(item);
                setSelectedItems(newSet);
                return;
            }
            copiedSet.add(item);
        }
        setSelectedItems(copiedSet);
    };

    return [
        {
            selectedItems,
        },
        {
            handleItemSelect,
        },
    ];
};
```

To continue the concept of `Bikes` and `TennisRacquets`, if we have two distinct lists of items for our Bikes and Racquets, we could use the above code sample to handle selection for both `types`.

### Tennis Racquet Example

```tsx
import { useSelect } from "../hooks";

function TennisRacquetList({ racquets }: { racquets: ITennisRacquet[] }) {
    const [{ selectedItems }, { handleItemSelect }] = useSelect(
        new Set<string>()
    );
    return (
        <div className="card-wrapper">
            {racquets.map((racquet) => (
                <TennisRacquet
                  key={racquet.GUID}
                  isActive={selectedItems.has(racquet.GUID)}
                  tennisRacquet={racquet}
                  selectOne={handleItemSelect}
                  multiSelect={handleItemSelect}
                />
            ))}
        </div>
    );
}

export default TennisRacquetList;
```

### Bike Example

```tsx
import { useSelect } from "../hooks";

function Bikes({ bikes }: { bikes: IBike[] }) {
    const [{ selectedItems }, { handleItemSelect }] = useSelect(
        new Set<number>()
    );
    return (
        <div className="card-wrapper">
            {bikes.map((bike) => (
                <Bike
                  key={bike.id}
                  isActive={selectedItems.has(bike.id)}
                  bike={bike}
                  selectOne={handleItemSelect}
                  multiSelect={handleItemSelect}
                />
            ))}
        </div>
    );
}

export default Bikes;
```

## Summary

Generic Typescript types allow reusable functionality for logic that may be very similar but only differ by type. Instead of needing to create [**type predicates**](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) or type narrowing in this use-case, we can instead use `TypeScript Generics` to achieve reusability, and maintainability in the instance that another type is requesting this same functionality.

## References

- [TypeScript Generics Tutorial](https://www.javatpoint.com/typescript-generics)
- [TypeScript Generic Example](https://www.educba.com/typescript-generic/)
- [Type Narrowing in TypeScript](https://www.rainerhahnekamp.com/en/type-safe-typescript-with-type-narrowing/)
- [Custom Hooks](https://beta.reactjs.org/learn/reusing-logic-with-custom-hooks)

## Acknowledgements

A special thanks to the wonderful team behind this engagement and learning: [Bryan](https://www.linkedin.com/in/bryan77/), [Ivan](https://www.linkedin.com/in/ivan-sholokh/), [Shreyas](https://www.linkedin.com/in/ranadeshreyas/), [Maggie](https://www.linkedin.com/in/maggiemarxen/), [Bret](https://www.linkedin.com/in/bstateham/), [Michael](https://www.linkedin.com/in/mscherotter/), and [Maarten](https://www.linkedin.com/in/maartenvandebospoort/).

The post [Getting to Know TypeScript Generics](https://devblogs.microsoft.com/cse/2023/05/17/getting-to-know-typescript-generics/) appeared first on [CSE Developer Blog](https://devblogs.microsoft.com/cse).