Using generics in Arrow functions in TypeScript

avatar

Borislav Hadzhiev

Last updated: Apr 16, 2022

banner

Photo from Unsplash

Using generics in Arrow functions in TypeScript #

You can use a generic in an arrow function by setting it right before the function's parameters, e.g. const returnInArray = <T>(value: T): T[] => {}. The generic can then be passed when the function is invoked, e.g. returnInArray<string>('hello').

index.ts
const returnInArray = <T>(value: T): T[] => { return [value]; }; const strArray = returnInArray<string>('hello'); const numArray = returnInArray<number>(100);

Generics allow us to pass types as a variable to functions and classes.

The generics are specified right before the function's parameters using arrow brackets.

The syntax for calling a function that has a generic is the same - it's passed right before the function's arguments.

Note that we didn't have to explicitly provide the generic when calling the function. In this situation TypeScript would be able to infer the type of the generic based on the type of the passed in argument.

index.ts
const returnInArray = <T>(value: T): T[] => { return [value]; }; // 👇️ const strArray: string[] const strArray = returnInArray('hello'); // 👇️ const numArray: number[] const numArray = returnInArray(100);

We can apply constraints around generics to only allow certain types to be passed.

index.ts
type CanRun = { run(): void; }; // 👇️ Can only be called with object that has run() method const callRun = <T extends CanRun>(obj: T) => { obj.run(); }; // 👇️ the animal runs callRun({ run: () => console.log('the animal runs') }); class Dog { run() { console.log('the dog runs'); } } callRun(new Dog()); // 👉️ the dog runs
The callRun arrow function can only be called with an object that has a property run that is a function with a return type of void.

If we try to call the function with a type that doesn't satisfy the CanRun type, we would get an error.

index.ts
type CanRun = { run(): void; }; const callRun = <T extends CanRun>(obj: T) => { obj.run(); }; // ⛔️ Argument of type 'number' is not assignable // to parameter of type 'CanRun'.ts(2345) callRun(100);

This pattern is very commonly used when you need a guarantee that a function can only be called with an object that contains certain properties.

Said object could be of multiple types, but as long as it contains the specified properties, it can be used as an argument for the function.

index.ts
class Shark { swim() { console.log('The shark swims'); } } class Dolphin { swim() { console.log('The dolphin swims'); } } interface CanSwim { swim(): void; } const callSwim = <T extends CanSwim>(obj: T): void => { obj.swim(); }; callSwim<Dolphin>(new Dolphin()); callSwim<Shark>(new Shark());

The callSwim function takes an object parameter and calls the swim method on it.

The function uses a constraint to make sure that it only gets passed objects that contain a swim property of type function.

You can also use generics in arrow functions in classes.

index.ts
class GenericArray { public arr: (string | number)[] = []; insert = <T extends string | number>(el: T): void => { this.arr.push(el); }; print = (): void => { console.log(this.arr); }; } const ga1 = new GenericArray(); ga1.insert<string>('a'); ga1.insert<number>(1); ga1.print(); // 👉️ ['a', 1] // ⛔️ Argument of type '{ hello: string; }' is not assignable // to parameter of type 'string | number'.ts(2345) ga1.insert({ hello: 'world' });

The insert class method can either be passed a string or a number.

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.