TypeScript Generics Explained: Real-World Patterns for React Developers

Generics are TypeScript's most powerful feature for writing reusable, type-safe code. Many React developers skip them because the syntax looks intimidating. But once you see generics through the lens of real component and hook patterns, they become indispensable — and surprisingly readable.
Generic Components That Adapt to Their Props
A generic component accepts a type parameter that flows through its props, state, and return values. The classic example is a typed list component:
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 — TypeScript infers T from the items array
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
TypeScript infers T from the items prop. You get autocomplete for user.name and user.id — no explicit type annotation needed at the call site. This pattern eliminates dozens of nearly identical component variants.
Type-Safe Custom Hooks
Generics shine in custom hooks where input and output types are structurally related. A useFetch hook demonstrates this cleanly:
interface UseFetchResult<T> {
data: T | null
isLoading: boolean
error: Error | null
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
fetch(url)
.then((res) => res.json() as Promise<T>)
.then(setData)
.catch(setError)
.finally(() => setIsLoading(false))
}, [url])
return { data, isLoading, error }
}
// Usage — typed response without casting
const { data } = useFetch<User[]>('/api/users')
// data is typed as User[] | null
The generic parameter flows from the call site through the entire hook. A real production hook would handle cancellation, caching, and race conditions, but the generic foundation is the same.
Utility Types That Reduce Boilerplate
Combine generics with TypeScript's built-in utility types to transform your existing types without manual redefinitions:
// Type-safe state setter that only accepts valid keys
function createStateUpdater<T extends object>(obj: T) {
return function <K extends keyof T>(key: K, value: T[K]) {
return { ...obj, [key]: value }
}
}
type ApiResponse<T> = {
data: T
status: number
message: string
}
// Pick specific fields for a table view
type TableUser = Pick<User, 'id' | 'name' | 'email'>
The K extends keyof T constraint is one of the most useful patterns in React development. It ensures you can only pass valid property keys to updaters, reducers, or column definitions.
Generic Reducers for Complex State
When useReducer manages complex state, generics keep the actions type-safe across large codebases:
type ActionMap<T extends { [key: string]: unknown }> = {
[K in keyof T]: { type: K; payload: T[K] }
}
type ProductActions = ActionMap<{
SET_NAME: string
SET_PRICE: number
SET_TAGS: string[]
RESET: undefined
}>
type ProductAction = ProductActions[keyof ProductActions]
This pattern ensures every dispatched action carries the correct payload type. No more runtime crashes because a reducer received a string where it expected a number.
When Generics Go Too Far
Generics add complexity. Do not use them for one-off components or hooks used in a single context. Reserve generics for abstractions that genuinely serve multiple data types — list components, form field wrappers, data-fetching hooks, and reducer action maps. Simple functions should stay simple.
Mastering TypeScript generics transforms how you structure React applications. The result is code that catches type errors at compile time rather than in production. At SoniNow, we write type-safe React applications where generics eliminate entire categories of runtime bugs before they reach your users.
Want to level up your TypeScript architecture? Contact SoniNow for a code review or to discuss how we can bring type safety to your next project.
Related Insights

Building Accessible React Applications: WCAG 2.2 Compliance Guide
A guide to building WCAG 2.2 compliant React applications including semantic HTML, ARIA attributes, keyboard navigation, focus management, and automated accessibility testing.

Authentication Patterns in Modern Web Apps: JWT, Sessions, and Passkeys
A guide to modern authentication patterns comparing JWT, session-based auth, and passkeys including implementation strategies, security considerations, and user experience.

Code Splitting and Lazy Loading in React: Performance Optimization Guide
A comprehensive guide to code splitting and lazy loading in React applications including React.lazy, Suspense, route-based splitting, and component-level chunking.