This site uses tracking technologies. You may opt in or opt out of the use of these technologies.

Back to Typescript

Typescript Generic Types: Master generics for writing reusable components

Mastering TypeScript generics is an essential skill for creating reusable and type-safe components, especially in a frontend development context like React.

1. Basic Generic Types

Generics allow functions, classes, and interfaces to work with any data type. Here’s a basic example:

function identity<T>(arg: T): T {
return arg;
}
  • <T> is the generic type variable. Here, T can be any type.

  • You can use identity<number>(5) or identity<string>("hello") to specify the type, or let TypeScript infer it.

2. Using Generics in Interfaces and Classes

Generics are helpful for creating reusable interfaces or classes.

interface KeyValuePair<K, V> {
key: K;
value: V;
}
class DataStore<T> {
private data: T[] = [];
add(item: T) {
this.data.push(item);
}
getAll(): T[] {
return this.data;
}
}
  • Here, KeyValuePair<K, V> is a generic interface that can handle different types for key and value.

  • DataStore<T> is a generic class storing any type of data specified by T.

3. Constraining Generics

Sometimes, you’ll want to limit the types a generic can accept using constraints with extends.

function printName<T extends { name: string }>(obj: T) {
console.log(obj.name);
}
  • This function only accepts objects with a name property.

  • Example usage: printName({ name: "Amir", age: 30 });

4. Default Generic Types

TypeScript allows you to set default types for generics, which makes them optional.

interface ApiResponse<T = any> {
data: T;
error?: string;
}
  • If no type is specified, ApiResponse will default to any.

  • You can override it like ApiResponse<string>.

5. Generics in React Components

Generics are invaluable in React components, allowing flexible prop types.

interface ListProps<T> {
items: T[];
renderItem: (item: T) => JSX.Element;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <>{items.map(renderItem)}</>;
}
  • This List component can render any type of item array and use any item-rendering logic.

  • Usage: <List items={['Apple', 'Banana']} renderItem={(item) => <p>{item}</p>} />

6. Utility Types with Generics

TypeScript offers utility types like Partial, Readonly, and Record, which you can use to work with generics:

function updateObject<T>(obj: T, updates: Partial<T>): T {
return { ...obj, ...updates };
}
  • Here, Partial<T> makes all properties in T optional.

  • updateObject can apply partial updates to an object of any type T.

7. Extending Types for More Specificity

You can extend one generic type with another, combining types with constraints for specific scenarios:

function getProp<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
  • Here, K extends keyof T means that K can only be a property key from T.

  • This function ensures the safety of accessing properties on an object.

8. Practical Example in React - Generic Form Component

Suppose you want a generic form component that can handle any kind of data object.

interface FormProps<T> {
initialValues: T;
onSubmit: (values: T) => void;
}
function Form<T>({ initialValues, onSubmit }: FormProps<T>) {
const [values, setValues] = useState<T>(initialValues);
const handleChange = (key: keyof T, value: any) => {
setValues((prevValues) => ({ ...prevValues, [key]: value }));
};
return (
<form onSubmit={() => onSubmit(values)}>
{Object.keys(values).map((key) => (
<input
key={key}
value={(values as any)[key]}
onChange={(e) => handleChange(key as keyof T, e.target.value)}
/>
))}
</form>
);
}
  • Form<T> can handle any object type passed as initialValues and submit that type safely.

  • Use it with different data structures, e.g., <Form initialValues={{ name: '', age: 0 }} onSubmit={handleSubmit} />.

Generics in TypeScript improve the reusability, readability, and safety of your code by enforcing type constraints dynamically. Once you’re comfortable, they become a powerful tool for building scalable frontend applications.