back to log

TypeScript Tips for React Developers.

Practical TypeScript patterns that make your React code safer and more maintainable without adding complexity.

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.

snippetTSX
// 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:

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

2. Component Props Patterns

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

snippetTSX
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:

snippetTSX
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:

snippetTSX
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:

snippetTSX
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:

snippetTSX
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:

snippetTSX
// 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.

✱   ✱   ✱
Y
Yashika Jotwani
Software developer & designer · Delhi