Typescript Generics

Complete Guide to Using Typescript Generics

December 15, 2022
David Ozokoye
React.js

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Introduction

Modern software development best practices involve writing reusable and readable codes. The concept of Generics provides the toolbox needed for creating reusable components.

Programming languages such as C# and Java also use generics to create components that work with various types without modifying the original component. TypeScript generics works in the same way as well.

In this tutorial, you’ll learn all you need to know about integrating TypeScript generics into your projects. Before we begin, here is a brief introduction to TypeScript generics and the advantages you stand to gain by using it.

What Are Generics in TypeScript?

Generics are placeholders or type parameters used to create reusable components in TypeScript. It allows you to initialize a function or a class without specifying the data types it accepts until it is called.

The idea behind implementing generics was to allow users to enter the type of data they’d like to pass on a function. Traditionally, you can use the any type to allow all data types on your function. However, when you use the any data type, you lose the benefits TypeScript provides, which is type checking.

With typescript generics, you can declare a type variable for updating the data type on your functions.

TypeScript generics can be used in functions, classes, interfaces, and types. We will cover all of them in this tutorial, but for now, let’s understand the basic syntax using a function.

Generics Syntax

In its simplest form, generics have the following syntax:

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

To define a generic type, we use the <> angle brackets. Within the bracket, you can type a name for the type you are defining. The name you use doesn’t matter as long as it is consistent throughout the function. But we generally use the letter T to declare a generic type.

In the example() function above, we define a function that accepts an argument of type T, where T is a placeholder for the actual type the user can pass into the function.

The function’s return value is of the same type as well. After creating the function, we can use it in the following way.

let sampleFn = example<string>("My example code")
console.log(samepleFn);

In the code above, we explicitly define the generic type as a string when calling the function.

There is an alternative way of setting the generic type parameter of the function. This method allows the compiler to infer the data type based on the type you pass into the function.

let inferFn = example(1200);

In the code above, we didn’t have to explicitly pass the type into the function before using it. The compiler knows to infer the type as number from the example above.

type inference in typescript

Type inference can be useful to shorten your overall code base, however, you may need to explicitly pass the generic types when dealing with a more advanced example class.

Advantages of TypeScript Generics

If you are a TypeScript developer, then you already know the benefits it adds to your JavaScript codes. Additionally, generics provide the following advantages.

  • It helps in creating type parameters or placeholders to hold generic data types.
  • It helps in creating reusable components that are error-free.
  • It enforces uniformity in argument and return value.
  • Allows developers to use flexible and scalable code snippets.
  • It integrates well with React applications.

If you are looking to boost your product launch timeline, using the CopyCat Figma plugin will help you to build UI faster than your competitors.

Getting Started With TypeScript Generics

In this section, we’ll explain how TypeScript generics work in detail with example codes.

Prerequisites

To follow along with this tutorial, you’ll need to have:

  • Node JS and the NPM installer on your machine.
  • TypeScript installed on your machine
  • Some knowledge of JavaScript, functions, and classes.
  • Basic knowledge of TypeScript, types, and interfaces.

If you are still new to TypeScript, the video below should get you up to speed.

With that out of the way, let’s explain how TypeScript generics works.

Using Generics With Functions

By now you should have an idea of how generic function works as it is the most popular use case of generics. We’ll share some example implementations of generic functions.

To start, consider the following function.

function identity(value) {
  return value;
}

We could update the identity function to make it a generic type function.

function identity<T>(value: T): T {
  return value;
}

In the code above, we added a generic type parameter T to the function. It also accepts an argument of the same type and will have the same return value.

We can use the identity function by directly passing in the data type in angle brackets.

const result = identity<number>(123);

Alternatively, we can directly pass the value we’d like and TypeScript will infer the type automatically using Type Inference.

const result = identity("I love CopyCat's Figma Plugin");
console.log(typeof(result));

When you run the above function, it will return the string type as the data type of the function.

Using Generics With Classes

So far, we’ve seen generic function implementation. However, there are other structures you can use such as generic classes. This allows us to build reusable classes that’ll accept the type we specify.

Let’s see how this works by implementing an example of generic classes.

class MySampleClass<T>{
...
}

You can add multiple type parameters to the generic type.

class MySampleClass<K,T>{
...
}

Here is a more practical implementation of a generic class.

class MultipleTypes<T,U>
{ 
    private key: T;
    private value: U;

    setKeyValue(key: T, value: U): void { 
        this.key = key;
        this.value = value;
    }

    display():void { 
        console.log(`Key = ${this.key}, value = ${this.value}`);
    }
}

In the code above, we are creating a generic class that accepts 2 generic type parameters, T and K. We then create 2 private variables that’ll each use one of the generic type parameters.

The setKeyValue() method serves as a constructor function to initialize the variables. Finally, we have a display method to log the key and value passed into this class.

We can call this class and pass in the types that’ll be allowed in the generic class.

let keyValue = new MultipleTypes<number, string>();
keyValue.setKeyValue(1, 'David');
keyValue.display(); 

Here is the output from the example code above.

key value pair

Generic classes can also implement a generic interface. We’ll explain more about using generics with interfaces in the next section. For now, let’s consider the example class below.

interface IKeyValue<T, U>
{
    process(key: T, val: U): void;
};

class KeyValProcessor<T, U> implements IKeyValue<T, U>
{ 
    process(key:T, val:U):void { 
        console.log(`Key = ${key}, val = ${val}`);
    }
}

let user: IKeyValue<number, string> = new KeyValProcessor();
user.process(2, 'Claire');

In the code above, we first created a generic interface that will accept 2 generic parameters. After that, we are creating a class that’ll implement the generic interface.

Using Generics With Interfaces and Types

Interfaces and Types are custom data types that allow you to customize the structure of objects, functions, and classes. In this section, we’ll share some examples of using Generics with Interfaces and Types.

Interfaces

Interfaces are custom types or specific structures to a function or an object. We can use generics to define the types allowed in the interface we are creating.

It works similarly to how you’d define it on a function or a class. Let’s create an interface for collecting user data.

interface Resource<T> {
  uid: number;
  resourceName: string;
  data: T;
}

In the code snippet above, we created a generic interface that’ll accept any data type we enter in the data property.

We can use this interface to create an object or a string data type. This makes the code reusable and flexible.

const personOne: Resource<object> = {
  uid: 1,
  resourceName: 'person',
  data: {
    name: 'David Ozokoye',
    age: 40,
  }
}

We are creating a person object based on the Resource interface. Since it is a generic interface, we can specify that the data property should be an object.

console.log(personOne);

We can log this to ensure everything works as expected

generic interface with objects

The benefit of using a generic interface is that it allows us to enter any data type in the type variable. For instance, we can use a strings array instead of an object and it would work as expected.

const listOne: Resource<string[]> = {
  uid: 1,
  resourceName: 'Shopping List',
  data: ['item 1', 'item 2']
}

console.log(listOne);

Here is the response from the code snippet above.

generic interface with strings array

Types

So far, we’ve seen how to use generics in functions, classes, and interfaces. Next, let’s see how to implement generics with Types.

The syntax follows similar principles to what we’ve discussed above.

type CustomType<T> = T

The generic type returns the same type parameter that’s passed into it.

Types are especially useful for creating mapped types. Consider the following example Type.

type Halfway<T> = {
  [P in keyof T]?: T[P];
};

The type Halfway here takes in a generic type, then iterates over its property types, and optionally returns them in a new type.

We’ll explain more about generic types when creating conditional and mapped generic types.

How to Create Mapped Types With Generics

Sometimes you’d need to create a type with the same shape and properties as another type. Implementing this in typescript is known as mapped types. Using mapped types reduces repeating code on your application because they are reusable components.

Imagine we want to create a type that should inherit features from another type. But needs to return a new type of boolean. Below is the basic implementation of mapped type in typescript.

type BooleanFields<T> = {
  [K in keyof T]: boolean;
};

In the code above, we are creating a generic type T. For this type, we are also creating a new type that should have the same shape as the type T. In addition, it’ll have its own unique shape.

We use the keyof T keyword to specify that K should have a union value with T. We can alternatively represent this as T[K] and it would have the same type.

The K in keyword specifies that the properties of the new type are all the properties available in the current union type returned by the keyof T keyword.

One practical use case of mapped type can be to allow users to optionally return values from a database using the boolean type. Here is a practical example.

type BooleanFields<T> = {
  [K in keyof T]: boolean;
};

type User = {
  email: string;
  name: string;
}

type UserFetchOptions = BooleanFields<User>;

In the code above, the UserFetchOptions type uses the BooleanFields type which is mapped to the User type. This means if the email field is false, it won’t be returned. It will only return true values.

How to Create Conditional Types With Generics

Conditional types are generic types that return different types based on certain conditions. If the condition you specify is met, it returns a particular type, otherwise, it won’t return any value. Consider the following example.

type isNumber<T> = T extends number ? true : false;

In the code above, we are creating a generic type isNumber that’ll receive a single type parameter, T. This type extends the number type in TypeScript. Then we are using a ternary operator like in JavaScript to conditionally check if type, T extends a number.

If it does, the resulting type will return true, otherwise, it will set the return value as false.

Here is a practical implementation of conditional types.

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type StringOrPromise = string | Promise<string>;

const value1: StringOrPromise = "hello";
const value2: StringOrPromise = Promise.resolve("world");

type UnwrappedString = UnwrapPromise<StringOrPromise>;

const unwrapped1: UnwrappedString = value1; // string
const unwrapped2: UnwrappedString = value2; // string

In the code above, we create the UnwrappedString type by applying the UnwrapPromise conditional type to the StringOrPromise type.

It will return a new type that is either string if the input type is not a promise or if the return type is also a promise.

If you’d like to learn more advanced examples of conditional and mapped types, be sure to check out the video below.

Generic Constraints

You can add constraints to TypeScript generics to further limit what’s allowed in the function or class. This can be helpful when adding multiple type variables to functions.

Before explaining generic constraints, let’s understand how multiple type variable works with the following example code.

function displayType<T, U>(id:T, username:U): void { 
  console.log(typeof(id) + ", " + typeof(username));  
}

displayType<number, string>(1, "david");

In the code above, we declare 2 type variables T and U which are the return values for the arguments the function expects.

Since the id and username return different values, we set the return value of the function as void.

Within the function, we are simply checking the typeof each argument and logging it to the console. When you run the code above, you should get the following response logged on the console.

number and string data type

Now we know how adding multiple type variables works, let’s see some examples of using generic constraints in TypeScript.

const includeUID = (obj: object) => {
  let uid = Math.floor(Math.random() * 50);
  return {...obj, uid};
}

let userOne = includeUID({name: 'david', age: 30})

console.log(userOne)

In the code above, we are creating a function that assigns a random ID to an object. It accepts any object as an argument value. When you run the code above, you should get the following output logged on the console.

console ouput for generic constriants

Anytime you run the code, a random value from 0-50 would be assigned as the uid.

The problem with this implementation is that we can’t access the components of the object. When you try to access the name parameter, you’d get an error that reads “Property ‘name’ does not exist on type ‘{ uid: number; }”.

Error accessing name property

We can add a generic type to the function and it should fix the issue. Replace the includeUID function with the code below.

const includeUID = <T>(obj: T) => {
  let uid = Math.floor(Math.random() * 50);
  return {...obj, uid};
}

When you run the code now, you can access the properties of the object.

accessing properties of an object

We’ve been able to access the object property by adding a generic type to the function.

However, there is another drawback to this implementation. Since there is no restriction to the type parameters, we can use a string type as the argument in the includeUID function and it would work just fine.

let userTwo = includeUID('david')

This is not the behavior we intend for this function. We need a way to restrict the data type that can be passed as a parameter.

We can add generic constraints to only allow object types in the function. To use the constraints, we use the extends keyword and enter the data type it should depend on.

const includeUID = <T extends object>(obj: T) => {
  let uid = Math.floor(Math.random() * 50);
  return {...obj, uid};
}

let userTwo = includeUID('david')

If you try to pass a string when calling the function, TypeScript will warn you that string is not assignable to an object type.

type error

Frequently Asked Questions

Below are answers to some of the questions developers ask about using TypeScript generics.

Does TypeScript support a generic interface?

Yes, it does. When it comes to generic interfaces, you can use the type keyword to define the data types and set aliases for your interfaces. You can refer to the Interfaces section to learn how to integrate generic types into interfaces.

What is the T in TypeScript?

The T in TypeScript is known as the Type variable. And it is used as a placeholder for the type that should be passed to a function or class.

Why do you need TypeScript Generics?

Generics in TypeScript are essential because it helps developers to build reusable components. It works with functions, classes, and interfaces. It also enforces uniformity in data types, making building components more flexible.

Conclusion

TypeScript generics provide a great way of creating reusable components. In this tutorial, you’ve learned how it works and how to create generic types for objects, functions, and classes.

We also covered advanced concepts such as Generics constraints and creating conditional types with generics.

If you’d like to use TypeScript on your React apps, check out our tutorial to learn the basics of integrating TypeScript with React. Also, check out the rest of our blog for more useful tutorials.

When you start building with React and TypeScript, CopyCat’s tool will help you build User Interfaces faster. You simply need to enter a link to your Figma designs. Our Figma to React converter will generate production-level code based on your designs.

Interesting Reads From CopyCat Blog

If you found this post useful, check out other useful content on your blog

Related Articles

  • React.js

    All You Need to Know About TypeScript Types

    The common data types in typescript will be covered in this article along with examples of how they are used. What is TypeScript Type System? TypeScript type system refers to the different data types and their supported operations that are…

    October 14, 2022
  • React.js

    Complete Guide to Bootstrap Tabs in ReactJS

    Introduction Bootstrap tabs are a popular component of the popular open-source front-end development toolkit, Bootstrap. It provides a way to create navigation elements on a web page quickly. This article will discuss how to use Bootstrap tabs to create navigation…

    December 20, 2022

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code