Back to Blog

TypeScript Tips for React Developers

Nov 2025·7 min read

TypeScript and React are a powerful combination, but it's easy to fall into patterns that add complexity without adding value. Here are practical tips I've learned from building real projects.

1. Let TypeScript Infer

One of the most common mistakes is over-annotating. TypeScript's inference is excellent — trust it.

// Unnecessary annotation
const [count, setCount] = useState<number>(0);

// Let it infer
const [count, setCount] = useState(0);

Only add explicit types when the initial value doesn't tell the full story:

const [user, setUser] = useState<User | null>(null);

2. Component Props Patterns

For components, define props as a type right above the component:

type CardProps = {
  title: string;
  description: string;
  tags: string[];
  href?: string;
};

export default function Card({ title, description, tags, href }: CardProps) {
  // ...
}

Use type instead of interface for props — it's more flexible and works better with unions and intersections.

3. Children Prop

React provides a built-in type for components that accept children:

type WrapperProps = {
  children: React.ReactNode;
  className?: string;
};

React.ReactNode covers everything: strings, numbers, elements, arrays, fragments, and null.

4. Event Handlers

Don't reach for any with event handlers. React's type system has you covered:

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.preventDefault();
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};

5. Discriminated Unions for State

Instead of optional fields, use discriminated unions to model state:

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

This makes it impossible to access data without first checking that the status is "success."

6. The `as const` Trick

When defining arrays of objects (like navigation links), use as const to get literal types:

const links = [
  { label: "Home", href: "/" },
  { label: "Blog", href: "/blog" },
] as const;

This narrows the types from string to the specific literal values, which catches typos at compile time.

7. Avoid Enums

Use union types instead of enums. They're simpler, tree-shakeable, and work better with the rest of TypeScript:

// Instead of enum
type Theme = "light" | "dark" | "system";

Final Thought

The goal of TypeScript isn't to annotate everything — it's to catch bugs before they reach users. Write the minimum amount of types needed to make your code safe, and let inference handle the rest.