TypeScript Patterns Every React Developer Should Know
Why TypeScript Matters in React
TypeScript isn't just about catching typos. It's about designing better components, documenting your APIs, and refactoring with confidence.
Here are the patterns I use every day in production React applications.
1. Generic Components
Generic components let you build reusable UI that preserves type information:
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>
);
}The caller gets full type safety on the render function without any type assertions.
2. Discriminated Unions for State
Instead of multiple boolean flags, use discriminated unions:
type State =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: User[] }
| { status: "error"; message: string };This makes impossible states impossible. You can't accidentally access data when the status is "error".
3. Polymorphic Components
Build components that can render as different HTML elements:
type ButtonProps<T extends React.ElementType> = {
as?: T;
children: React.ReactNode;
} & React.ComponentPropsWithoutRef<T>;
function Button<T extends React.ElementType = "button">({
as,
children,
...props
}: ButtonProps<T>) {
const Component = as || "button";
return <Component {...props}>{children}</Component>;
}Now Button can render as an a, Link, or any other element while keeping proper type checking.
4. Type-Safe Event Handlers
Always type your event handlers explicitly:
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// formData is properly typed
};5. Const Assertions for Config
Use as const to create narrow types from configuration objects:
const ROUTES = {
home: "/",
blog: "/blog",
about: "/about",
} as const;
type Route = (typeof ROUTES)[keyof typeof ROUTES];
// type Route = "/" | "/blog" | "/about"Conclusion
These patterns aren't theoretical — they're practical tools that make your React code more robust and easier to maintain. Start with discriminated unions and generic components. They'll change how you think about component design.
Jordan Lee
Building for the web since 2018. I write about React, Next.js, TypeScript, and the tools that make developers productive.