Back to Blog
TypeScriptReactJavaScriptPatterns

TypeScript Advanced Patterns for React Developers

8 May 2026 10 min read
TypeScript Advanced Patterns for React Developers
TypeScript Advanced Patterns for React

TypeScript transforms React development from "hope nothing breaks" to "the compiler guarantees correctness." After 3+ years building production React applications as a TypeScript developer, here are the advanced patterns I use daily.

Generic Components

Generic components let you create reusable UI that preserves type information. Consider a data table component:

interface Column<T> {
  key: keyof T;
  header: string;
  render?: (value: T[keyof T], row: T) => React.ReactNode;
}

interface DataTableProps<T> {
  data: T[];
  columns: Column<T>[];
  onRowClick?: (row: T) => void;
}

function DataTable<T extends { id: string }>({ data, columns, onRowClick }: DataTableProps<T>) {
  return (
    <table>
      <thead>
        <tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
      </thead>
      <tbody>
        {data.map(row => (
          <tr key={row.id} onClick={() => onRowClick?.(row)}>
            {columns.map(col => (
              <td key={String(col.key)}>
                {col.render ? col.render(row[col.key], row) : String(row[col.key])}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

When you use DataTable<User>, the columns array only accepts keys that exist on the User type. The onRowClick callback receives a fully typed User object. No runtime type errors, no defensive checks.

Discriminated Unions for State Management

Discriminated unions are the most powerful TypeScript pattern for managing async state in React. Replace boolean flags with typed states:

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function useAsync<T>(fetcher: () => Promise<T>): AsyncState<T> {
  const [state, setState] = useState<AsyncState<T>>({ status: 'idle' });

  useEffect(() => {
    setState({ status: 'loading' });
    fetcher()
      .then(data => setState({ status: 'success', data }))
      .catch(error => setState({ status: 'error', error }));
  }, []);

  return state;
}

TypeScript narrows the type based on the status discriminator. After checking state.status === 'success', you can access state.data without optional chaining or null checks.

Template Literal Types for Design Systems

Template literal types create type-safe design tokens that prevent invalid combinations:

type Color = 'primary' | 'secondary' | 'neutral';
type Shade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
type ColorToken = `${Color}-${Shade}`;

function useColor(token: ColorToken): string {
  return `var(--color-${token})`;
}

useColor('primary-500'); // ✓ Valid
useColor('tertiary-300'); // ✗ Type error

This is the foundation of my UI engineering work — type-safe design tokens that prevent the most common source of visual inconsistencies.

Conditional Types for API Responses

Conditional types create precise types for API responses that vary based on request parameters:

type ApiResponse<T, Include extends string[] = []> = 
  'user' extends Include[number]
    ? T & { user: User }
    : T;

async function fetchPost<Include extends string[] = []>(id: string, include?: Include) {
  const params = include ? `?include=${include.join(',')}` : '';
  const response = await fetch(`/api/posts/${id}${params}`);
  return response.json() as Promise<ApiResponse<Post, Include>>;
}

const post = await fetchPost('1', ['user']);
post.user.name; // Typed correctly because we requested 'user'

Key Takeaways

  • Generic components preserve type information through your entire component tree
  • Discriminated unions eliminate impossible states — fewer null checks, fewer bugs
  • Template literal types create self-documenting design tokens
  • Conditional types generate precise types from API parameters
  • TypeScript investment pays for itself in reduced debugging time and better developer experience

Written by Bhavya Panchal — Frontend Developer & UI Engineer

WORK WITH ME

Bhavya Panchal — Frontend Developer

Bhavya Panchal

Frontend Developer & UI Engineer based in Ahmedabad, India. 3+ years building fast, accessible web applications with React, Next.js, Remix, Shopify Hydrogen, and TypeScript. Available for freelance and full-time opportunities worldwide.