TypeScript Generics for React Engineers: A Practical Guide
The 6 generic patterns I use weekly on React + Next.js codebases — typed hooks, polymorphic components, discriminated unions, infer, constraints — without the academic noise.
Generics are not academic
When React engineers ask me "do I really need TypeScript generics?" the answer is almost always: you're already using them, you just haven't named them yet.
Every useState, every array map with inferred types, every fetch typed with a return shape — that's generics doing the work. You just lean on inference. When inference isn't enough, knowing the explicit patterns saves hours.
This post is the 6 patterns I reach for on every React or Next.js codebase, in roughly the order you'll need them.
Pattern 1: Typed custom hooks
The most common case. A hook returns data of a shape that depends on what the caller fetches.
A custom useFetch hook can be parameterized with the return type. The caller writes useFetch and gets fully typed data: User | null downstream.
Without the generic, data is unknown or any. With it, the caller's destructuring is autocompleted.
Pattern 2: Constrained generics
When the generic type must satisfy some shape. For example, a sortBy utility that operates over any object with an id field, using keyof T to narrow the key argument to a real property name.
The extends { id: string } is a constraint. T can be any object as long as it has an id. This pattern is gold for utility functions over collections of "things with an id" — orders, users, products, anything.
Pattern 3: Polymorphic components
The "as" prop pattern — a component that can render as different HTML elements. For example a that becomes an anchor with typed href.
This is the pattern Radix UI, Headless UI, and shadcn slash ui all use internally. The polymorphic typing means as="a" autocompletes anchor-specific props.
It's mildly mind-bending. Use a library that already does it (Radix Slot, Headless UI) instead of writing your own unless you're building a design system.
Pattern 4: Discriminated unions for state
The pattern I use for fetch states and forms. Model state as one of:
{ status: "idle" }{ status: "loading" }{ status: "success"; data: T }{ status: "error"; error: Error }TypeScript narrows inside each branch. In the success branch, state.data is typed; in error branch, state.error is typed. You can't accidentally access state.data when status is loading.
This catches an entire class of bugs at compile time.
Pattern 5: infer for extracting types
You'll need this less often, but when you do, it's magic. The infer keyword is how you extract a piece of a type. Useful for getting the resolved type of a promise, the params of a function, the props of a component.
In day-to-day React, you'll mostly use the built-in utilities: ReturnType, Parameters, Awaited, ComponentProps. They're infer under the hood.
Pattern 6: Generic context
Typed Context patterns for state management. A factory like createContext that returns a Provider plus a useTypedContext hook with built-in undefined guard.
The runtime check plus type narrowing is in the factory. Consumers never deal with undefined context.
What you don't need
Things I see overdone:
Quick mental model
When deciding whether to reach for a generic:
If you're not doing any of these, you don't need a generic. Inference does the work.
TL;DR
If your team is on a TypeScript codebase that's grown unwieldy and needs senior-level cleanup, reach out.
You might also like
Real-Time Apps with Next.js Server Actions and WebSockets in 2026
When Server Actions are enough, when you need a WebSocket layer, and how to wire Pusher / Soketi / Ably into a Next.js 14 App Router project without breaking SSR.
React Server Components in Next.js 14: Production Patterns That Work
When to use Server Components vs Client Components in Next.js 14, the patterns that survive production, and the foot-guns I keep tripping over after shipping 8+ App Router projects.
Next.js App Router vs Pages Router: Lessons From 12 Project Migrations
What changes, what breaks, what to migrate first, and what to leave alone. Hard-earned migration lessons from moving 12 client projects from Pages Router to App Router.