All Posts
JavaScript

TypeScript with React: Practical Patterns That Actually Save Time

September 22, 2025

TypeScript in a React codebase pays off fastest when you stick to a handful of patterns rather than chasing full type coverage on day one. These are the ones I actually use.

Typing Props

type ButtonProps = {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
};

function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
  return <button className={variant} onClick={onClick}>{label}</button>;
}

A union type like 'primary' | 'secondary' beats a plain string — it turns a typo into a compile-time error instead of a runtime bug.

Typing useState When Inference Isn't Enough

type User = { id: string; name: string } | null;
const [user, setUser] = useState<User>(null);

TypeScript infers the type fine for primitives, but for anything that starts as null or an empty array, an explicit generic saves you from a widened, mostly-useless type.

Typing Event Handlers

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  console.log(e.target.value);
}

React ships its own event types — reach for React.ChangeEvent, React.MouseEvent, etc. instead of the native DOM event types, since they include React's synthetic event behavior.

Generic Components

type ListProps<T> = {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
};

function List<T>({ items, renderItem }: ListProps<T>) {
  return <>{items.map(renderItem)}</>;
}

This is the pattern that makes a single List component reusable across completely different data shapes, with full autocomplete on renderItem's parameter — no any, no duplication.