# Understand TypeScript Generics

It is one of the most critical things in software development to create components that are not only well-defined and consistent but also reusable. Components that are capable of working on the data of today, as well as the data of tomorrow, will give you the most flexible capabilities for building up large software systems.

The purpose of this article is to demonstrate how TypeScript generics can be applied to functions, types, classes, and interfaces to make them dynamic and reusable.

![Generics in Typescript.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647755113846/VPnWbZHjO.png align="left")

## WHAT EXACTLY IS GENERIC?

> **In languages like TypeScript, C#, and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their types.**

> **Generics is a tool in TypeScript that allows users to create reusable structures by passing types as parameters to functions, types, classes, and interfaces. This structure can work with a variety of data types rather than just one. It ensures long-term scalability as well as flexibility for the program.**

## SETUP A PROJECT:

> To run the following code snippets, you need to have `typescript` installed globally. If you don't have `typescript` installed, run `npm install -g typescript` in your terminal and check out my blog on [Introduction to TypeScript](https://blog.wajeshubham.in/introduction-to-typescript#heading-lets-set-up-a-project) to set up a typescript project required for this article.

## INTRODUCTION TO GENERICS:

Whenever you create a generic structure, you must use `<>` to wrap the parameters that the generic structure accepts.

The following is the syntax for creating a generic structure:

### SYNTAX:

```typescript
function example<T>(arg: T): T {
  return arg;
}
```

For the moment, we have declared a generic function (we will delve into generic functions in upcoming topics in this article).

To the `example` function, we have added the type variable `T`. The `T` allows us to capture the type that the user provides as an argument (e.g. `number`) for later use. (`T` stands for `Type`, and is commonly used as the first type variable name when defining generics, but it can be replaced with any valid name.)

This function's return type and argument type are dependent on what we pass in while calling it.

Check out the following code to see how we can pass generic-type arguments:

```typescript
const returnedValue = example<string>('hello');
```

`string` was passed as a **TYPE** argument to the generic `example` function.

Let's hover over the `returnedValue` variable and look at the return type:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647974388706/E680AVFvJ.png align="left")

The return type is a `string`, which we passed as a type argument.

### ADDS STRICT TYPE CHECKING:

Generics have strict type-checking. Our code above passes a `string` as a **type argument**, so we must pass a `string` in the **function argument** as well.

In the case of any other type, we will encounter the following compilation error:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647756640928/Lq-jZTOeI.png align="left")

This is the beauty of generics in typescript because they are dynamic, yet strictly typed at the same time.

### ACCEPTS ANY VALID TYPE:

As opposed to passing `string` as a type argument in the above example, we can also pass any valid type or interface as follows:

```typescript
interface Person {
  name: string;
  age: number;
}

function example<T>(arg: T): T {
  return arg;
}

// return type will be Person
const returnedValue = example<Person>({
  name: "Max",
  age: 30,
});
```

As you can see, we have a `Person` interface which is a valid type in TypeScript.

It is passed as a type argument to the `example` function, meaning the function argument must be of type `Person`.

If we attempt to do anything outside the rules, we will get a compilation error:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647756956019/6MY4l6T9U.png align="left")

## BUILT-IN GENERICS:

In addition to creating our own generics, TypeScript has some built-in generics that can be used.

The following are some generics that TypeScript offers:

### `Array<T>` TYPE:

My previous articles have discussed this `Array` type and referred to it as a generic type.

It is probably one of the most commonly used generics in TypeScript.

Here's how to declare a variable's type using the `Array` keyword:

```typescript
const names: Array<string> = ["John", "Jane", "Mary"];
```

Here, we have a variable called `names`, and we have declared it as an `Array<string>` *(array of strings)*. In other words, it's the equivalent of `string[]`, which we have used in the past.

What happens if we don't pass `string` as a type argument to the `Array` type?

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647757617275/pEf1kcnst.png align="left")

It says `Generic type 'Array<T>' requires 1 type argument(s).` This means we need to pass a type argument to the `Array` keyword.

What happens if we pass a different type of value in an array of type `Array<string>`?

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647757741321/FchqiHdW_.png align="left")

Similar to our `example` function, this compiles with an error if we pass a different type value in a generic structure.

### `Promise<T>` TYPE:

`Promise` is another built-in generic type. It expects only one `type argument`.

> If you want to learn more about promises, check out my detailed article on [Promises in JavaScript](https://blog.learncodeonline.in/promises-in-javascript).

Let's take an example to better understand the `Promise` type. Look at the following code.

```typescript
const _promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Success!");
  }, 2000);
});

_promise.then((data) => {
  console.log(data);
});
```

The above code creates a `_promise` variable, which is a `Promise` that returns a `string` when it is resolved.

Our promise is then triggered with a `.then()` block and the resolved value is captured in the `data` variable.

Let's hover over the `_promise` variable and see what type is inferred:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647758682887/zuuJHmd6c.png align="left")

The type shown is `Promise<unknown>`, which is not beneficial since we know that it will resolve to a `string`.

Therefore, to make it happen, we can use `Promise` type and pass `string` as a type argument to it as follows:

```typescript
const _promise: Promise<string> = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Success!");
  }, 2000);
});

_promise.then((data) => {
  // type of data is string
  console.log(data);
});
```

Now, if we add `.` in front of `data` in the `.then()` block, we will get autosuggestions of all string methods available:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647758840510/Y6MKJGtCD.png align="left")

> We will see how we can use `Promise` and `Axios` to write scalable API functions in the next part of this article.

### `Readonly<T>` TYPE:

`Readonly` is a generic, built-in type that allows us to throw errors whenever a method that alters or mutates the original structure is used.

It constructs a type with all properties of `T` set to `readonly`. See for yourself:

```typescript
let x: Readonly<number[]> = [1, 2, 3];

x.push(4); // error
x.length = 0; // error
x[0] = 12; // error

x.map((v) => v); // without error
x.filter((v) => v > 1); // without error
```

```typescript
let y: Readonly<{
  name: string;
  age: number;
}> = {
  name: "John",
  age: 30,
};

y.age = 20; // error!
y.name = "John"; // error!
```

When you want an array/object that never changes, this is useful.

### `Partial<T>` TYPE:

It constructs a type with all properties of `T` set to optional.

Take a look at the following example:

```typescript
interface Course {
  title: string;
  price: number;
  description: string;
  rating: number;
}

// same as { title?: string; price?: number; description?: string; rating?: number; }
let course: Partial<Course> = {};
```

`Partial` will make all the properties of the `Course` interface optional.

### `NonNullable<T>` TYPE:

`NonNullable<T>` prevents you from passing `null` or `undefined` to your structure. This complements the `strictNullChecks` compiler flag in the `tsconfig.json` file, so make sure you activate it *(ignore it if you have* `strict` flag set to `true`):

Let's use `NonNullable` in our `example` function code.

```typescript
function example<T>(arg: T): T {
  return arg;
}

let a = example<string | null>(null); // runs without error
```

Now the type argument is a `string | null` union type. The code above executes without error.

We'll see what happens with `NonNullable`:

```typescript
function example<T>(arg: NonNullable<T>): T {
  return arg;
}

let a = example<string | null>(null); // throws error
```

Even though a `null` could be passed to our generic example function as a function argument, the `NonNullable` type internally discards it.

## GENERIC FUNCTIONS:

We looked at how we can accept type as a parameter in the first topic of this article by using the `example` function.

Here are some more complex examples of generic functions.

### FUNCTION TO MERGE TWO OBJECTS:

Suppose we want to create a merge function that takes two arguments, both are objects and returns the merged object.

```typescript
function mergeObjects(obj1: object, obj2: object) {
  return { ...obj1, ...obj2 };
}

const mergedObj = mergeObjects({ name: "Max" }, { age: 30 });
```

We have the `mergeObjects` function here, which merges the `obj1` and `obj2` of type `object` and returns the merged `object`.

Then we call the function with some arguments and store the returned data in the `mergedObj` variable.

Let's hover over the `mergedObj` variable and see what the inferred type is.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647761979974/yqi60nKLx.png align="left")

We have `{}` (an object), which is not beneficial, because when we access the `age` property on the `mergedObj` variable, we will receive a compilation error as follows:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647762142397/zKKAK0rVX.png align="left")

But as a developer, we know that if we merge `{ name: "Max" }` and `{ age: 30 }`, we should get `{ name: "Max", age: 30 }` in return.

To accomplish this, let's convert this function into a generic function.

### CONVERT IT TO A GENERIC FUNCTION:

```typescript
function mergeObjects<T, U>(obj1: T, obj2: U) {
  return { ...obj1, ...obj2 };
}

const mergedObj = mergeObjects({ name: "Max" }, { age: 30 });
```

The `mergeObjects` method accepts two `type parameters` `T` and `U`. These type parameters are assigned to `obj1` and `obj2` respectively.

Now, if we call the `mergeObjects` function with the same function arguments. `T` and `U` will automatically become the type inferred from the passed arguments. *(We don't have to specify the type for* `T` and `U`, TypeScript automatically manages that for us)

Thus, `T` will be assigned as `{name: string}` and `U` will be assigned as `{age: number}`.

If we hover over the `mergedObj` variable we will see the following inferred type:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647762392138/HXEhJEI39.png align="left")

The `mergedObj` variable got inferred as an `intersection type` including `{name: string}` and `{age: number}`. *(To learn more about intersection types, click* [*here*](https://blog.wajeshubham.in/advance-typescript#heading-intersection-type)*)*

Now if we try to access the `age` property on the `mergedObj` variable, we will get autosuggestions from the IDE and will not get compilation errors, as follows:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647762614466/OCUuwudBr.png align="left")

### IT IS DYNAMIC:

A generic function is useful because now you can pass literally any key in the object, and the function will automatically detect which keys are available in the merged objects.

Here's an example containing more keys in one of the arguments of the `mergeObjects` function.

```typescript
function mergeObjects<T, U>(obj1: T, obj2: U) {
  return { ...obj1, ...obj2 };
}

const mergedObj = mergeObjects(
  { name: "Max", skills: ["typescript", "javascript"], salary:"5000" },
  { age: 30 }
);
```

Let's see what the autosuggestions have to offer:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647762957128/n0AQL1kZ_.png align="left")

Generic functions are great for this.

## GENERIC CONSTRAINTS:

There is a problem with the above `mergeObjects` function. The compiler will not throw an error if we pass any type of value as an argument.

Look at the following image:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647763579228/shLVAZC5M.png align="left")

This is a problem because only and only `objects` should be allowed as arguments to the `mergeObjects` function to avoid unexpected results.

To do so, we can use **generic constraints** to add more type-checking.

### `extends` KEYWORD:

So our logic is that we want to make `mergeObjects` generic, but we can only accept arguments of type `object`.

To accomplish that, we can do the following:

```typescript
function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U) {
  return { ...obj1, ...obj2 };
}

const mergedObj = mergeObjects({ name: "Max", age: 20 }, 30); // throws error

console.log(mergedObj);
```

By using the `extends` keyword, we tell TypeScript that the `mergeObjects` function can accept any type of argument, but it must be of type `object`.

Now if we try to pass `number` as an argument, we get a compilation error:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647765638723/LfNS357TN.png align="left")

### `keyof` KEYWORD:

Let's use an example to understand the use case for the `keyof` keyword:

```typescript
function getValueFromObj<T extends object, U>(obj: T, key: U) {
  return obj[key];
}

getValueFromObj({ name: "John" }, "name");
```

In the above function, we are accepting two parameters `obj` and `key`. `obj` is of `T` type which extends the `object` type, and `key` is of `U` type. Next, we are returning the value from the `obj` object by using the `key` parameter.

There is a compilation error here since TypeScript has no idea whether that `key` is present in the `obj` or not.

For the above code to be valid, we can use the `keyof` keyword to only accept the `key` which exists inside an `obj` object.

```typescript
function getValueFromObj<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key];
}

getValueFromObj({ name: "John" }, "name");
```

We are adding code here to make `U` only accept keys that are contained within the `obj` object.

Passing a key that does not exist in a passed object will cause a compilation error as follows:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647767270370/qK9TlAtl6.png align="left")

## GENERIC CLASSES:

Generic classes have a generic type parameter list in angle brackets (&lt;&gt;) following the name of the class as follows:

```typescript
class ClassName<T> {}
```

### `MyStorage` CLASS EXAMPLE:

Let's create a class that stores an array of data and performs operations on that data.

Check out the following code:

```typescript
class MyStorage {
  private storedData: any[] = [];

  public setItem(value: any) {
    this.storedData.push(value);
  }

  public removeItem(index: number) {
    this.storedData.splice(index, 1);
  }

  public getItems() {
    return this.storedData;
  }
}
```

In the previous blogs, we learned that we should never use `any` keyword unless there are no other options, and no type definitions are available for the piece of code you're working on.

The above `MyStorage` class can be converted to a generic class to make it more strongly typed and dynamic.

### MAKE `MyStorage` CLASS GENERIC:

Check out the following code.

```typescript
class MyStorage<T> {
  private storedData: T[] = [];

  public setItem(value: T) {
    this.storedData.push(value);
  }

  public removeItem(index: number) {
    this.storedData.splice(index, 1);
  }

  public getItems() {
    return this.storedData;
  }
}
```

We have a `MyStorage` class that is generic, accepting a type parameter `T`, and has `storedData` property that is of type `T[]` (an array of the type T).

Additionally, it has `setItem`, `removeItem`, and `getItems` methods.

Let's create an instance of it.

```typescript
class MyStorage<T> {
  private storedData: T[] = [];

  public setItem(value: T) {
    this.storedData.push(value);
  }

  public removeItem(index: number) {
    this.storedData.splice(index, 1);
  }

  public getItems() {
    return this.storedData;
  }
}

const stringStorage = new MyStorage<string>();
```

For `stringStorage`, the `T` will be a `string`. Therefore, the `storedData` property of the `MyStorage` class will be of type `string[]`.

Let's add some items to our storage now.

```typescript
const stringStorage = new MyStorage<string>();

stringStorage.setItem("Hello");
stringStorage.setItem("World");
stringStorage.removeItem(1);
console.log(stringStorage.getItems()); // ["Hello"]
```

Everything works fine!

### IDE SUPPORT:

Similarly, we can create storage for complex types using the `MyStorage` class.

```typescript
interface Employee {
    name: string;
    age: number;
}

const employeeStorage = new MyStorage<Employee>();

employeeStorage.setItem({ name: 'John', age: 30 });
employeeStorage.setItem({ name: 'Alex', age: 29 });

let employees = employeeStorage.getItems();
console.log(employees); // prints [{name: 'John', age: 30}, {name: 'Alex', age: 29}]
```

If we try to operate on the `employees` variable, we will receive the following autosuggestions from the IDE:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647769984836/iwOHr3lI9.png align="left")

### STRICTLY TYPED AND FLEXIBLE:

Once you specify what type of data the `MyStorage` class preserves, you cannot pass different types of arguments to its methods. If you do so, you will receive an error such as the following:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647770026817/wdX6kaO3D.png align="left")

This is the beauty of a generic class. It is flexible and strictly typed at the same time.

## GENERIC INTERFACES:

We can implement generic interfaces in a similar way to generic classes. It almost works the same way.

To understand the syntax, let's look at an example:

```typescript
interface KeyPair<T, U> {
  key: T;
  value: U;
}

let kp1: KeyPair<number, string> = { key:1, value:"Steve" }; 
let kp2: KeyPair<number, number> = { key:1, value:12345 };
```

As you can see in the above example, by using the generic interface as type, we can specify the data type of `key` and `value`.

In a similar way to classes, we can pass dynamic types to the interface. So, let's see how we can use generics in a real-world project to make the most of it.

## GENERICS WITH `Axios` AND `React`:

`Axios` is a simple promise-based HTTP client for the browser and node.js. It provides a simple-to-use library in a small package with a very extensible interface.

Most commonly, it is used when working with `React` applications. Let's see how generics can be used to make the most of API calls.

### SETUP A `React` AND `TypeScript` PROJECT:

Run the following command in your terminal to set up a React TypeScript project:

```bash
npx create-react-app react-axios-typescript --template typescript --use-npm
```

* By using the `--template typescript` flag, a react project with preinstalled `typescript` and `tsconfig.json` configuration will be generated.
    
* The `--use-npm` flag is optional. If you're using `yarn` and `npm` at the same time. And you want to tell `create-react-app` to use `npm` instead of `yarn` to generate the project.
    

Open the generated project in VS Code.

### INSTALL & CONFIGURE `axios`:

Use the following command to install `axios` *(because* `axios` comes with built-in type declarations, so we don't have to install `@types/axios` separately)

```bash
npm install axios
```

Now, create the `api/index.ts` file inside the `src` folder. The folder structure should look like this:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647849216643/MrHpMcvPX.png align="left")

Let's configure our `axios` client and create the `ApiService` class to write API calls.

In your `api/index.ts` file, add the following code:

```typescript
import axios from "axios";

const axiosClient = axios.create({
  // url which is prefixed to all the requests made using this instance
  baseURL: "https://jsonware.com/api/v1/json"
});

class ApiService {
  static async getCourseById(id: string) {
    return axiosClient.get(`/${id}`);
  }
}

export default ApiService;
```

In the code above, we have created an `axios` instance `axiosClient` with `baseURL` *(*`baseURL` is the URL that will get prefixed to all requests sent via `axiosClient`. Whenever building a large-scale application, this is a good practice to create multiple instances).

Furthermore, we created a class `ApiService` which will hold all the `static` methods for API calls. *(To learn more about static methods/properties in typescript, click* [*here*](https://blog.wajeshubham.in/classes-in-typescript#heading-static-properties-andamp-methods)*)*

`getCourseById` is an asynchronous method that uses `axiosClient` for an API call.

As you hover your cursor over the `getCourseById` method, you will see its return type is as follows:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647850004271/kpxmsyB-l.png align="left")

It is `Promise<AxiosResponse<any, any>>`.

Now, let's break this!

I have explained that the `Promise` is a generic type in one of the previous topics in this article.

In other words, this method returns a `Promise` that, when resolved, will return a response of the type `AxiosResponse<any, any>`.

### AXIOS BUILT-IN GENERIC INTERFACE FOR RESPONSE:

Let's now look at what `AxiosResponse<any, any>` is.

It is a built-in `interface` provided by the `Axios` library. Let's look at its declaration and see how it's structured.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647850262100/jFuC2JI2s.png align="left")

As you can see in the image above. `AxiosResponse` is a generic interface that expects two `type parameters`, `T` and `D`, which are by default assigned to be of `any` type.

`T` is assigned to the `data` key and `D` is assigned to the `config` key. Let's ignore the `config` key declaration for now since we generally don't modify it.

When we receive a response from the backend, it is generally accessible through the `data` key.

Therefore, we can create a custom interface for the `data` key and pass it as the first type argument to `AxiosResponse`.

Let's declare an interface that represents the response we receive from the back end.

To do that, create the file `type/interface.ts` in the `src` folder as follows:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647850771504/0_YqLS9z9.png align="left")

Add the following code to `type/interface.ts`:

```typescript
export interface ServerResponse {
  status: number;
  errorMessage: string;
  success: boolean;
  message: string;
  result: any;
}
```

The above interface shows the structure of a server response *(which is set by the backend developers)*. As developers, we know that we will receive the following keys with the success response from the backend: *(There might be different keys for different backends, it depends on how the backend response is structured)*

* `status` of type `number`.
    
* `errorMessage` of type `string`.
    
* `success` of type `boolean`.
    
* `message` of type `string`.
    
* `result` of type `any` because multiple responses may have different data types for the `result` key so it makes sense to use `any` here.
    

Now let's pass the above interface to `AxiosResponse` as the first type argument as follows:

```typescript
...
class ApiService {
  static async getCourseById(
    id: string
  ): Promise<AxiosResponse<ServerResponse, any>> { // additional code
    return axiosClient.get(`/${id}`);
  }
}
...
```

### RESPONSE AUTOSUGGESTIONS:

Let's try this method out in the `App.tsx` file and see what its benefits are.

Add the following code to the `App.tsx` file:

```typescript
import React, { useEffect } from "react";
import "./App.css";
import ApiService from "./api";

function App() {
  const fetchCourseById = async () => {
    try {
      const response = await ApiService.getCourseById(
        "90794953-d58f-4cde-b2ce-9e0a6643c658"
      );
      console.log(response);
    } catch (error) {
      console.log(error);
    }
  };

  useEffect(() => {
    fetchCourseById();
  }, []);

  return (
    <div className="App">
      <h1>Learn typescript</h1>
    </div>
  );
}

export default App;
```

Let's see what the console displays.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647852377704/-LgmwUa96D.png align="left")

If you look closely, you can see that the response structure is exactly the same as that of the `AxiosResponse` interface.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647852538529/qfTzpnjDF.png align="left")

Let's see if we get suggestions for the `data` key for this `AxiosResponse` in the `fetchCourseById` function.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647852622122/aRS1wBs_E.png align="left")

Yes, we got the autosuggestions because the data key in `AxiosResponse` is of type `ServerResponse`, which is our interface.

You can type your `axios` response this way if you have a common response structure coming from the backend. So that you can eliminate errors caused by accessing a key that does not exist or is incorrect from the server response.

## CONCLUSION:

* In this tutorial, you explore generics as they apply to functions, interfaces, classes, and custom types. You also used generics constraints for additional type checking.
    
* Each of these makes generics a powerful tool you have at your disposal when using TypeScript. Using them correctly will save you from repeating code over and over again, and will make the types you have written more flexible.
    
* This is especially true if you are a library author and are planning to make your code legible for a wide audience.
    

Make sure to subscribe to our newsletter on [https://blog.wajeshubham.in/](https://blog.wajeshubham.in/) and never miss any upcoming articles related to TypeScript and programming just like this one.

I hope this post will help you in your journey. Keep learning!

My [***Website***](https://wajeshubham.in), connect with me on [LinkedIn](https://www.linkedin.com/in/shubham-waje/) and [GitHub](https://github.com/wajeshubham)
