typescript-generics
Apply when writing reusable functions, components, or utilities that work with multiple types while maintaining type safety.
When & Why to Use This Skill
This Claude skill provides comprehensive patterns and best practices for implementing TypeScript generics. It enables the creation of flexible, reusable, and type-safe code structures, ranging from simple generic functions to complex conditional types and React components. By mastering these patterns, developers can eliminate code duplication and significantly reduce runtime errors through robust compile-time type checking.
Use Cases
- Developing reusable API utility functions that automatically infer and preserve return types based on input parameters.
- Building generic React UI components (e.g., Tables, Lists, or Forms) that provide full IntelliSense support for diverse data models.
- Implementing complex data transformation layers using conditional types to handle dynamic success and error states.
- Refactoring legacy codebases to replace 'any' types with flexible generics, enhancing maintainability and developer experience.
- Creating type-safe utility libraries that maintain strict constraints while allowing for broad type compatibility.
| name | typescript-generics |
|---|---|
| description | Apply when writing reusable functions, components, or utilities that work with multiple types while maintaining type safety. |
| version | 1.1.0 |
| tokens | ~750 |
| confidence | high |
| - https | //www.typescriptlang.org/docs/handbook/2/conditional-types.html |
| last_validated | 2025-12-10 |
| next_review | 2025-12-24 |
| tags | [typescript, generics, types] |
When to Use
Apply when writing reusable functions, components, or utilities that work with multiple types while maintaining type safety.
Patterns
Pattern 1: Generic Function
// Source: https://www.typescriptlang.org/docs/handbook/2/generics.html
function first<T>(array: T[]): T | undefined {
return array[0];
}
const num = first([1, 2, 3]); // number | undefined
const str = first(['a', 'b']); // string | undefined
Pattern 2: Generic with Constraints
// Source: https://www.typescriptlang.org/docs/handbook/2/generics.html
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
// Works with any object that has 'id'
const user = findById(users, '123');
const post = findById(posts, '456');
Pattern 3: Generic React Component
// Source: https://www.typescriptlang.org/docs/handbook/2/generics.html
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage with full type inference
<List
items={users}
renderItem={(user) => user.name} // user is typed as User
keyExtractor={(user) => user.id}
/>
Pattern 4: Conditional Types
// Source: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
type ApiResponse<T> = T extends undefined
? { success: true }
: { success: true; data: T };
// Result: { success: true }
type VoidResponse = ApiResponse<undefined>;
// Result: { success: true; data: User }
type UserResponse = ApiResponse<User>;
Pattern 5: Multiple Generics
// Source: https://www.typescriptlang.org/docs/handbook/2/generics.html
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
// T = User, U = string
const names = map(users, user => user.name);
// Merge objects
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
Pattern 6: Generic Defaults
// Source: https://www.typescriptlang.org/docs/handbook/2/generics.html
interface PaginatedResult<T = unknown> {
data: T[];
page: number;
total: number;
}
// Uses default
const result: PaginatedResult = { data: [], page: 1, total: 0 };
// Explicit type
const users: PaginatedResult<User> = { data: [], page: 1, total: 0 };
Pattern 7: NoInfer Utility (TypeScript 5.4+)
// Source: https://www.typescriptlang.org/docs/handbook/utility-types.html
// Blocks inference to ensure parameter matches previously inferred type
function createStreetLight<C extends string>(
colors: C[],
defaultColor?: NoInfer<C>
) {
return { colors, defaultColor };
}
createStreetLight(['red', 'yellow', 'green'], 'red'); // OK
createStreetLight(['red', 'yellow', 'green'], 'blue'); // Error
Anti-Patterns
- Generic where not needed - Don't use if only one type ever used
- Too many generics - Keep under 3 for readability
- No constraints - Add
extendsto limit valid types anyinstead of generic - Generics preserve type info
Verification Checklist
- Generic names are descriptive (T, TItem, TResponse)
- Constraints added where types need structure
- Default types provided where sensible
- Type inference works without explicit annotation
- Complex generics have usage examples