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)
oridentity<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 forkey
andvalue
.DataStore<T>
is a generic class storing any type of data specified byT
.
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 toany
.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 inT
optional.updateObject
can apply partial updates to an object of any typeT
.
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 thatK
can only be a property key fromT
.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) => (<inputkey={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 asinitialValues
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.