Until now, our types were fixed. string is always string.
But, what if we want to create a function or component that works with any type, but without losing safety?
Here enter Generics. They are like “parameters” but for Types.
The Problem: Duplication or any
Imagine a function that returns what you pass to it (classic identity function).
// Option 1: Duplicate code for each type
function returnString(val: string): string { return val; }
function returnNumber(val: number): number { return val; }
// Option 2: Use 'any' (We lose the type)
function returnAny(val: any): any { return val; }
const result = returnAny("Hello");
// TS doesn't know 'result' is string, thinks it's 'any'.
// result.toUpperCase(); // ❌ No autocomplete// Option 1: Duplicate code for each type
function returnString(val: string): string { return val; }
function returnNumber(val: number): number { return val; }
// Option 2: Use 'any' (We lose the type)
function returnAny(val: any): any { return val; }
const result = returnAny("Hello");
// TS doesn't know 'result' is string, thinks it's 'any'.
// result.toUpperCase(); // ❌ No autocompleteThe Solution: Generics <T>
We can tell the function: “Hey, we are going to pass a type to you that we will call T. Use it to type the argument and the return”.
function identity<T>(val: T): T {
return val;
}
// Explicit usage (we pass the type)
const num = identity<number>(123); // num is number
// Inference (TS is smart and guesses it)
const str = identity("Hello"); // str is "Hello" (literal) or stringfunction identity<T>(val: T): T {
return val;
}
// Explicit usage (we pass the type)
const num = identity<number>(123); // num is number
// Inference (TS is smart and guesses it)
const str = identity("Hello"); // str is "Hello" (literal) or stringThe T is a convention (comes from Type), but we can call it whatever we want: <Data>, <Response>, <Props>.
Generic Interfaces
This is super common in React or API responses.
// A box that can contain anything
interface Box<T> {
content: T;
label: string;
}
const shoeBox: Box<string> = {
content: "Nike Air",
label: "Sports"
};
const giftBox: Box<number> = {
content: 1000,
label: "Money"
};// A box that can contain anything
interface Box<T> {
content: T;
label: string;
}
const shoeBox: Box<string> = {
content: "Nike Air",
label: "Sports"
};
const giftBox: Box<number> = {
content: 1000,
label: "Money"
};Real Example: useState
If we have used React, we have already used Generics without knowing it.
// useState is a Generic function: function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]
// Inference:
const [name, setName] = useState("Alice"); // TS infers that T is string
// Explicit (useful when initial value is null):
const [user, setUser] = useState<User | null>(null);// useState is a Generic function: function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]
// Inference:
const [name, setName] = useState("Alice"); // TS infers that T is string
// Explicit (useful when initial value is null):
const [user, setUser] = useState<User | null>(null);Let’s try it!
Create a function envolver (wrap) that whatever you put in, returns it inside an array.
If you put 10, [10] comes out. If you put "hello", ["hello"] comes out.