Understanding Generics in Typescript
Shashwat Nautiyal
Posted on January 1, 2023
Learn how generics in TypeScript can save your time in development
What are generics in TypeScript?
Generics in typescript give you the advantage of writing functions or classes where that single function or class can operate with any data type like an array, string, number, or object.
Ex- You want to create a stack data structure that can be used with any data type or write a custom hook for API services that returns typed data instead of “any” type.
Let us understand with code 🧑🏻💻
class Stack<StackType> {
private items: StackType[];
private top: number;
constructor() {
this.items = [];
this.top = -1;
}
push(element: StackType) {
this.items[++this.top] = element
}
pop() {
return this.items[this.top--];
}
peek(){
return this.items[this.top];
}
isEmpty(){
return this.top === 0;
}
size(){
return this.top + 1;
}
}
let stack = new Stack<string>();
Explanation 🙇🏻♂️
Here we have created a generic stack class that accepts the type argument in angle brackets “<>”. This stack class will create an items array of type parameter StackType and the push function will only accept the StackType in a parameter.
Ex- If we create a number Stack by let stack = new Stack<number>()
then stack.push("hello world")
will throw an error. You can only pass numbers in the push function like stack.push(1)
.
Diving deep into generics
There are mainly 3 main types of generics
- Generic classes
- Generic functions
- Generic constraints
Generic classes are mainly used to create a generic data structure that can have multiple methods and variables. The type argument is passed only once in the generic class when it is instantiated.
class Todo<TodoType> {
todos: TodoType[];
constructor() {
this.todos = [];
}
addTodo(todo: TodoType) {
this.todos.push(todo);
}
getTodos() {
return this.todos;
}
}
Generic functions are functions that can accept any data type and return data accordingly. This makes the function more reusable and flexible.
let todos: any[] = [];
function addTodo<TodoType>(todo: TodoType) {
todos.push(todo);
}
Generic constraints are very similar to generic types but it uses the extend keyword to force the user to add required parameters in a function call. The generic constraints add the constraint in the generic type function or class.
function removeKey<Type, Key extends keyof Type>(obj: Type, key: Key) {
const _obj = {...obj}
delete _obj[key];
return _obj;
}
function updateTodo<TodoType extends {
id: number
}>(newTodo: TodoType) {
todos.forEach((todo, index) => {
if(todo.id === newTodo.id) {
todos[index] = {todo: removeKey(newTodo, "id"), id: newTodo.id}
}
})
}
In the above example, the “extends” keyword ensures that the id property exists in the newTodo and we can use the id in the function to find the todo and update it accordingly.
Code sandbox
How generics can help you in React?
You can take advantage of generics in React to create generic functions, custom hooks, or generic components. I will give an example of each which can help you to write your generics.
Use-cases 👀
Generic custom hook for fetching API
export const useFetch = <DataT>(api: string) => {
const [status, setStatus] = useState<
"fetching" | "success" | "error"
>("fetching")
const [data, setData] = useState<DataT>();
const [error, setError] = useState<string>();
useEffect(() => {
const fetchApi = async () => {
try {
setStatus("fetching")
const res = await fetch(api);
const data = await res.json();
setData(data);
setStatus("success")
} catch(err) {
setError(err)
setStatus("error")
}
}
fetchApi();
}, [api])
return {data, status, error} as const;
}
const { data, status, error } = useFetch<{
title: string,
description: string,
id: number
}>(API);
Generic function for creating an array of mixed data type
function createArray<Type>(
...values: Type[]
): Type[] {
return [...values]
}
const myArray = createArray<number | string>(
"Hello", "World", 2022
);
Generic component that renders a list without using map
const List = <T extends unknown>({
data,
keyExtractor,
renderItem
}: {
data: T[];
keyExtractor: (item: T) => string | number;
renderItem: (item: T) => React.ReactNode;
}) => {
return (
<>
{data.map((item) => (
<React.Fragment key={keyExtractor(item)}>
{renderItem(item)}
</React.Fragment>
))}
</>
);
};
<List
data={data}
keyExtractor={({ id }) => id}
renderItem={(item) => <div>{item.title}</div>}
/>
Code sandbox
Conclusion
In this article, we saw how generics can help you to write more reusable functions, components, and custom hooks. We also saw the different use cases of generics that helps you to build your logic with generics in React. It also provides a very good experience with IntelliSense integration in the IDE.
Posted on January 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.