Using the TypeScript generic type to create reusable components
Tags: typescript, webdev
Published: 2023-02-15 18:37:30

**Written by [Ishan Manandhar](https://blog.logrocket.com/author/ishan-manandhar/)✏️**

## What are TypeScript generics?

Generics in TypeScript are a method for creating reusable components or functions that can handle multiple types. Generics are a powerful tool that can assist us in creating reusable functions. They allow us to build data structures without needing to set a concrete time for them to execute at compile time. 

In TypeScript, they serve the same purpose of writing reusable, type-safe code where the type of variable is known at compile time. This means that we can dynamically define the type of parameter or function that will be declared beforehand. This comes in really handy when we need to use certain logic inside of our application; with these reusable pieces of logic, we can create functions that take in and give out their own types. 

We can use generics to implement checks at compile time, eliminate type castings, and implement additional generic functions across our application. Without generics, our application code would compile at some point, but we may not get the expected results, which could possibly push bugs into production. This powerful feature can help us create reusable, generalized, and type-safe classes, interfaces, and functions. 

In this post, we’ll learn how to achieve type safety via generics without sacrificing performance or efficiency. We can write a type parameter between angle brackets: `<T>`. We can also create generic classes, generic methods, and generic functions in TypeScript. 

_Jump ahead:_

*   [TypeScript generics in action](#typescript-generics-action)
    *   [Functions without using generics](#functions-without-using-generics)
    *   [Functions using generics](#functions-using-generics)
    *   [TypeScript classes using generics](#typescript-classes-using-generics)
*   [Using generics inside TypeScript interfaces](#using-generics-inside-typescript-interfaces)
*   [Passing default generic values to generics](#passing-default-generic-values-generics)
*   [Passing multiple generic values](#passing-multiple-generic-values)
*   [Adding constraints to generics](#adding-constraints-generics)

## TypeScript generics in action <a name="typescript-generics-action">

### Functions without using generics

Let’s consider the following example. Below, we have a simple function that will output the reverse order of what we provide in the array. We will name our function `removeRandomArrayItem`, which looks like:

```typescript
function removeRandomArrayItem(arr: Array<number>): Array<number> {
 const randomIndex = Math.floor(Math.random() * arr.length);
 return arr.splice(randomIndex, 1);
}

removeRandomArrayItem([6, 7, 8, 4, 5, 6, 8, 9]);
```

The function above tells us that the function name is `removeRandomArrayItem` and that it will take a parameter of `item`, which is a type of array consisting of numbers. Finally, this function returns a value, which is also an array of numbers. 

As you can see, we have already introduced a few constraints inside of our code. Let’s say we want to loop through an array of numbers instead. Should we build another function to handle this use case? 

No! Here is the sweet spot where TypeScript generics kicks in.

### Functions using generics

Let’s take a look into the problem before we write our solution using generics. If we pass the above function an array of numbers, it would throw us this error:

```plaintext
Argument of type 'number[]' is not assignable to parameter of type 'string[]'
```

We can fix this by adding `any` to our type declaration:

```typescript
function removeRandomArrayItem(arr: Array<any>): Array<any> {
 const randomIndex = Math.floor(Math.random() * arr.length);
 return arr.splice(randomIndex, 1);
}

 console.log(removeRandomArrayItem(['foo', 1349, 6969, 'bar']));
```

But there is no valid reason to use TypeScript if we aren’t dealing with any data types. Let’s refactor this piece of function to convert using generics.

```typescript
function removeRandomArrayItem<T>(arr: Array<T>): Array<T> {
 const randomIndex = Math.floor(Math.random() * arr.length);
 return arr.splice(randomIndex, 1);
}

console.log(removeRandomArrayItem(['foo', 'bar']));
console.log(removeRandomArrayItem([45345, 3453]));
```

Here, we denoted a type named `<T>`, which will make it to act more generic. This will hold the type of data that is received by the function itself.

### TypeScript classes using generics

```typescript
class Foo {
 items: Array<number> = [];

 add(item: number) {
   return this.items.push(item);
 }

 remove(item: Array<number>){
   const randomIndex = Math.floor(Math.random() * item.length);
   return item.splice(randomIndex, 1);
 }
}

const bar = new Foo();

bar.add(22);
bar.remove([1345, 45312613, 13453]);
```

Here, we created a simple class named `Foo`, which contains a variable that is an array of numbers. We also created two methods: one that adds items to the array and one that removes a random element from the array. 

This piece of code works well, but we have introduced the same problem as before: if we add or remove items in the array, the generic will only take in array of numbers. Let’s refactor this class to use a generic to accept a generic value, so we can pass any type to the argument.

```typescript
class Foo<TypeOfFoo> {
 items: Array<TypeOfFoo> = [];

 add(item: TypeOfFoo) {
   return this.items.push(item);
 }

 remove(item: Array<TypeOfFoo>){
   const randomIndex = Math.floor(Math.random() * item.length);
   return item.splice(randomIndex, 1);
 }
}

const bar = new Foo();

bar.add(22);
bar.add('adfvafdv');
bar.remove([1345, 45312613, 13453]);
bar.remove([1345, 45312613, '13453']);
```

With the use of generics inside classes, we have made our code much more reusable and DRY. This is where generics really shine!

## Using generics inside TypeScript interfaces

Generics are not specifically tied to functions and classes. We can also use generics in TypeScript inside of a interface as well. Let’s take a look at an example of how can we use it in action:

```typescript
const currentlyLoggedIn = (obj: object): object => {
 let isOnline = true;
 return {...obj, online: isOnline};
}

const user = currentlyLoggedIn({name: 'Ben', email: 'ben@mail.com'});

const currentStatus = user.online
```

With the above lines written, we get an error with a squiggly line telling us that we cannot access the property of `isOnline` from the user:

```plaintext
Property 'isOnline' does not exist on type 'object'.
```

This is primarily because the function `currentlyLoggedIn` does not know the type of object it is receiving through the object type we added to the parameter. We can get around this by making the use of a generic:

```typescript
const currentlyLoggedIn = <T extends object>(obj: T) => {
 let isOnline = true;
 return {...obj, online: isOnline};
}

const user = currentlyLoggedIn({name: 'Ben', email: 'ben@mail.com'});

user.online = false;
```

The shape of the object we are currently dealing with in our function can be defined in an interface:

```typescript
interface User<T> {
 name: string;
 email: string;
 online: boolean;
 skills: T;
}

const newUser: User<string[]> = {
 name: "Ben",
 email: "ben@mail.com",
 online: false,
 skills: ["foo", "bar"],
};

const brandNewUser: User<number[]> = {
 name: "Ben",
 email: "ben@mail.com",
 online: false,
 skills: [2456234, 243534],
};
```

Here is another example of how we can use a interface with generics. We defined an interface with `Greet`, which will take in a generic, and that specific generic will be the type that is passed in to our `skills` property. 

With this, we can pass the desired value to the `skills` property in our `Greet` function.

```typescript
interface Greet<T> {
 fullName: "Ben Douglass";
 skills: T
 messageGreet: string
}

const messageGreetings = (obj: Greet<string>): Greet<string> => {
 return {
   ...obj,
   messageGreet: `${obj.fullName} welcome to the app`,
 skills: 'sd'
 };
};
```

## Passing default generic values to generics <a name="passing-default-generic-values-generics">

We can also pass in a default generic type to our generic. This is useful in cases where we don’t want to pass in the data type we are dealing with in our function by force. By default, we are setting it to a `number`.

```typescript
function removeRandomArrayItem<T = number>(arr: Array<T>): Array<T> {
 const randomIndex = Math.floor(Math.random() * arr.length);
 return arr.splice(randomIndex, 1);
}
console.log(removeRandomArrayItem([45345, 3453, 356753, 3562345, 3567235]));
```

This snippet reflects how we made use of the default generic type on our `removeRandomArray` function. With this, we are able to pass a default generic type of `number`.

## Passing multiple generic values

If we want our reusable blocks of functions to take in multiple generics, we can do the following:

```typescript
function removeRandomAndMultiply<T = string, Y = number>(arr: Array<T>, multiply: Y): [T[], Y] {
 const randomIndex = Math.floor(Math.random() * arr.length);
 const multipliedVal = arr.splice(randomIndex, 1);
 return [multipliedVal, multiply];
}
console.log(removeRandomAndMultiply([45345, 3453, 356753, 3562345, 3567235], 608));
```

Here, we created a modified version of our previous function so that we can introduce another generic parameter. We denoted it with the letter `Y`, which is set to a default type of `number` because it will multiply the random number we picked from the given array. 

Since we’re multiplying numbers, we are definitely dealing with a `number` type, so we can pass the default generic type of `number`.

## Adding constraints to generics <a name="adding-constraints-generics">

Generics allow us to work with any data types that are passed as arguments. We can, however, add constraints to the generic to limit it to a specific type. 

A type parameter can be declared that is limited by another type parameter. This will help us add constraints upon the object, making sure we don’t obtain a property that possibly doesn’t exist.

```typescript
function getObjProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
 return obj[key];
}

let x = { name: "Ben", address: "New York", phone: 7245624534534, admin: false };

getObjProperty(x, "name");
getObjProperty(x, "admin");
getObjProperty(x, "loggedIn"); //property doesn't exist
```

In the above example, we created a constraint to the second parameter the function receives. We can invoke this function with the respective arguments and everything will work, unless we pass a property name that does not exist in the object type with the value of `x`. This is how we can constrict object definition properties using generics.

## Conclusion

In this post, we explored how we can use generics, and created reusable functions inside our codebase. We implemented generics to create a function, class, interface, method, multiple interfaces, and default generics.

---
## [LogRocket](https://lp.logrocket.com/blg/signup): Full visibility into your web and mobile apps

[![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/signup)

[LogRocket](https://lp.logrocket.com/blg/signup) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

[Try it for free.](https://lp.logrocket.com/blg/typescript-signup)