Generics in Typescript and how to use them

Lucia Nazzaro
Lucia Nazzaro
Lead Frontend Developer
Published
0 views

Sometimes I wonder whether I love or hate Typescript. I see the benefits of implementing it in the majority of frontend solutions, but I struggle with the syntax and the mumbo jumbo names that don't tell me anything. "Generics" would be one of those, as the name kindly suggests too, it's a name that could mean anything 🤷 When I find myself in the situation where I think I have grasped something but I haven't quite, the best solution is to try to write something about it to bring in clarity!

So what are Generics?

Generics in TypeScript provide a way to create reusable data structures (arrays, stacks, queues, linked lists etc.), components and functions that can work with different data types.

Take for example:

1interface Dictionary<T> { 2 [key: string]: T; 3}

With generics you can create a container type that can hold different types of values based on needs, e.g.:

1const myDictionary: Dictionary<number> = { 2 age: 30, 3 height: 180, 4}; 5 6const myOtherDictionary: Dictionary<string> = { 7 name: 'John', 8 city: 'New York', 9};

This is useful when you want to create a flexible and type-safe container, such as a dictionary or a set. You'll be guaranteed, whether the structure will accommodate strings or numbers, that it will follow the interface Dictionary and expect an object with keys whose value will reflect the data you use it with.

Let's make another practical example; let's say you want to have a function that allows you to print whatever type of array in a particular format, like an ordered list. In this case, the type of the data passed is not relevant to you, you only care that it's presented in an ordered list.

1function printArray<T>(array: T[]): string { 2 const items: string = array.map(item => `<li>${item}</li>`).join(''); 3 return `<ol>${items}</ol>`; 4}

Now you can use this function to format the arrays before appending them to a div:

1function printArray<T>(array: T[]): string { 2 const items: string = array.map(item => `<li>${item}</li>`).join(''); 3 return `<ol>${items}</ol>`; 4} 5 6// An example function to append HTML content to a div element 7function appendToDiv(containerId: string, content: string): void { 8 const container = document.getElementById(containerId); 9 if (container) { 10 container.innerHTML = content; 11 } 12} 13 14// Our data types 15const numbers: number[] = [1, 2, 3, 4, 5]; 16const strings: string[] = ['apple', 'banana', 'orange']; 17 18// Our data types formatted through the printArray function 19const numbersListHTML: string = printArray(numbers); 20const stringsListHTML: string = printArray(strings); 21 22// Append the lists to an hypotetical div container that shows the list 23appendToDiv('orderedLists', numbersListHTML); 24appendToDiv('orderedLists', stringsListHTML);

In the example, printArray doesn't care whether the array inputted is a numeric or a string one, but it makes sure to guarantee type safety based on the data type you pass to it.

Not bad, right?


What is a somewhat more practical application of generics in the real-life coding world?

Generics can be useful when working with promises and asynchronous operations, allowing you to specify the type of the resolved value.

1// Function that returns a promise to fetch data from an API 2function fetchData<T>(url: string): Promise<T> { 3 return fetch(url) 4 .then(response => { 5 if (!response.ok) { 6 throw new Error('Network response was not ok'); 7 } 8 return response.json() as Promise<T>; 9 }) 10 .catch(error => { 11 console.error('Error fetching data:', error); 12 throw error; 13 }); 14} 15 16// Usage 17interface UserData { 18 id: number; 19 name: string; 20 email: string; 21} 22 23const apiUrl = 'https://api.example.com/users/1'; 24 25fetchData<UserData>(apiUrl) 26 .then(data => { 27 // Here, 'data' will be of type UserData 28 console.log('User Data:', data); 29 }) 30 .catch(error => { 31 console.error('Error fetching user data:', error); 32 });

While this example is not perfect, it offers us a great way to dynamically query for various URLs, and for each case we have the possibility to provide the type we expect to receive: we query for https://api.example.com/users/1 - we expect to receive a response containing user information - so we provide the interface UserData to the function fetchData<UserData>(apiUrl) and if the request doesn't fail, our response will be already using the correct interface. Neat!


Challenges with Generics

Not all that shines is gold, and the same can be said about generics. While they offer this great versatility, you can imagine they also provide a lot of complexity to a codebase, as they will require developers to pay better attention to the data flow and make sure that things won't go as unexpected.

Together with the broader learning curve proposed to developers, there are also various limitations of type inferring that might make generics limiting to a practical application. No idea about those limits, will let you know when I stumble on those :D

Long story short

If you use them, make sure it makes sense on why you're using them and that they actually bring benefits on what you're doing. If you're working on a npm package that needs to accommodate different solutions (idk, a table with columns that can sort according to the data type provided, where you might not know ahead of time what values will be entered), generics might be very well what you're looking for! Bear in mind however, that they will always create an extra layer of complexity that you need to account for your code and for all your team mates!




More Articles

Fixing Disqus 'Auto' theme switching when using Next.js + next-themes

So, you want to have both a light and dark theme in your Next.js website, but your Disqus embed doesn't seem to change its theme accordingly... Maybe this will help!

ReactNext.js
+1

A Blasting August with Blaugust

Having a hard time finding will to work on your blogging website? Blaugust might just be that little spice of inspiration you were looking for!