Tillbaka till bloggen
Development9 min read

TypeScript Patterns for Better React Applications

AT
Alex Thompson
28 november 2024

TypeScript and React are a powerful combination. Here are patterns that will improve your type safety and developer experience.

Discriminated Unions for State

Instead of boolean flags, use discriminated unions:

// Avoid
type State = {
  loading: boolean;
  error: Error | null;
  data: Data | null;
};

// Better: Discriminated union
type State =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'error'; error: Error }
  | { status: 'success'; data: Data };

This pattern eliminates impossible states and makes handling each case explicit.

Generic Components

Create reusable components with generics:

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>
  );
}

Polymorphic Components

Build components that can render as any element:

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>;
}

// Usage
<Button>Click me</Button>
<Button as="a" href="/path">Link button</Button>

Type-Safe Event Handlers

Avoid any in event handlers:

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  const value = e.target.value;
  // value is typed as string
}

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  const formData = new FormData(e.currentTarget);
}

Const Assertions for Config

Use const assertions for configuration objects:

const ROUTES = {
  home: '/',
  about: '/about',
  blog: '/blog',
} as const;

type Route = typeof ROUTES[keyof typeof ROUTES];
// Type: '/' | '/about' | '/blog'

Conclusion

These patterns leverage TypeScript's type system to catch errors at compile time and improve the development experience. Start incorporating them gradually into your codebase.

Author
AT
Alex Thompson
Lead Developer

Alex is a full-stack developer with 8+ years of experience building web applications. He specializes in React, Next.js, and cloud architecture.