The Dev Blog
Back to blog
TypeScriptReact

TypeScript Patterns Every React Developer Should Know

JL
Jordan Lee
April 12, 20267 min read

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.

JL

Jordan Lee

Building for the web since 2018. I write about React, Next.js, TypeScript, and the tools that make developers productive.

Stay in the loop

Get notified when I publish new articles. No spam, unsubscribe anytime.