Published on

TypeScript: Generics in Arrow Functions, a complete guide

Generics are a powerful feature in TypeScript that allow you to define type variables that can be used in functions, classes, and interfaces. They provide a way to write code that can be reused with different types, making your code more flexible and .... generic 😎

Arrow functions are another feature in TypeScript that provide a concise syntax for defining functions. They can be used for simple one-liner functions or for more complex functions with multiple statements.

When combining generics and arrow functions, you can create highly flexible and reusable code that can work with a variety of data types. In this post, we'll explore how to use generics in arrow functions in TypeScript.

Table of Contents

Syntax for Generics in Arrow Functions

The syntax for defining a generic arrow function in TypeScript is similar to that of a regular arrow function. The main difference is that you use angle brackets (< >) to define the type variable, and then use the variable in the function definition. Example:

ts-generic-arrow.tsx
// Defining a generic arrow function
const genericArrow = <T>(arg: T): T => {
    return arg
}

//Using the generic arrow function
const str = genericArrow<string>('Returns a string'); // Returns "Returns a string"
const num = genericArrow<number>(64); // Returns 64

Here, the generic arrow function called genericArrow that takes a single argument of type T returns the same type. The type variable T is defined between angle brackets (< >) immediately after the function name.

When using the genericArrow function, we provide the type argument (<string>or <number>) to specify the type of the argument passed to the function.

Benefits of Using Generics in Arrow Functions

Using generics in arrow functions provides several benefits, including:

  • Flexibility: Generics allow you to write code that can work with a variety of data types, making your code more flexible and reusable.

  • Type safety: Generics provide type safety by allowing you to define the types of the arguments and return values of the function.

  • Code reusability: Generics make your code more reusable by allowing you to write functions that can be used with different data types.

Generics with Constraints

ts-generic-arrow.tsx
interface Member {
    fullName: string
    subscriptionID?: number
    subscriptionType: 'Classic' | 'Premium' | 'Gold'
}

const filterBy = <T, K extends keyof T>(
prop: K,
value: T[K]
) => (arr: T[]): T[] => {
    return arr.filter((item) => item[prop] === value)
}

const members: Member[] = [
{ fullName: 'Mike', subscriptionType: 'Classic' },
{ fullName: 'John', subscriptionType: 'Premium' },
{ fullName: 'Anna', subscriptionType: 'Gold' },
{ fullName: 'Peter', subscriptionType: 'Classic', subscriptionID: 123 },
]

const filteredMembers = filterBy<Member, 'subscriptionType'>(
'subscriptionType',
'Classic'
)(members)

console.log(filteredMembers) // Returns: [{ fullName: 'Mike', subscriptionType: 'Classic' }, { fullName: 'Peter', subscriptionType: 'Classic', subscriptionID: 123 }]

Here, the arrow function filterBy takes two generic type parameters T and K, and returns another arrow function that takes an array of type T[] and returns the filtered array of type T[].

The filterBy function takes two arguments, prop of type K and value of type T[K], where K extends keyof T ensures that prop is a key of the T interface. The returned arrow function takes an array of type T[] and uses the filter method to filter the array based on the property prop and its value value.

Generics with Constraints in Classes

ts-generic-arrow-class.tsx
interface Member {
    fullName: string
    subscriptionID?: number
    subscriptionType: 'Classic' | 'Premium' | 'Gold'
}

class MemberList<T extends Member> {
    private members: T[] = [];

    add(member: T) {
        this.members.push(member)
    }

    filterBy<K extends keyof T>(prop: K, value: T[K]): T[] {
        return this.members.filter((item) => item[prop] === value)
    }
}

const classicMembers = new MemberList<Member>()

    classicMembers.add({ fullName: 'Mike', subscriptionType: 'Classic' })
    classicMembers.add({ fullName: 'John', subscriptionType: 'Classic', subscriptionID: 123 })
    classicMembers.add({ fullName: 'Anna', subscriptionType: 'Gold' })
    classicMembers.add({ fullName: 'Peter', subscriptionType: 'Classic' })

    const filteredMembers = classicMembers.filterBy('subscriptionType', 'Classic')

    console.log(filteredMembers)// Returns: [{ fullName: 'Mike', subscriptionType: 'Classic' }, { fullName: 'John', subscriptionType: 'Classic', subscriptionID: 123 }, { fullName: 'Peter', subscriptionType: 'Classic' }]

In this case, we have a MemberList Class that takes a generic type parameter T that extends the Member interface. It has:

  • a private member array that holds the members added to the list.
  • add method that takes a T object and adds it to the members array.
  • a filterBy method that takes a generic type parameter K that extends the keys of T.

The filterBy method takes a property name prop of type K, and a value of type T[K] and returns an array of members that have the specified property value.

The new instance of MemberList<Member>, classicMembers,its been used to add some Member objects to the list and then filterBy to filter the list by the subscriptionType property.

Note: Here we force the filtering method of they array to be on a valid property K and valid value with T[K]

Generics in arrow functions are a powerful tool in TypeScript that can help you write more flexible and reusable code. By defining a type variable in an arrow function, you can create functions that work with a variety of data types, while still maintaining type safety. This can make your code more concise and easier to maintain in the long run.