# TypeScript for React

> How to type component props, event handlers, and hooks correctly. The contracts that prevent silent bugs.

**Principle:** Bugs caught at compile time cost nothing to fix. Bugs caught in production cost everything. TypeScript for React is not about learning the full TypeScript language — it is about writing the right contracts between your components so that mistakes are caught before the code even runs.

**Tip:** Start by typing your component props. If you can describe what a component accepts and returns, the rest of the types follow naturally.

**Rules:**
- **interface for component props** — Use interface to define component props. It is extendable and reads clearly as a contract.
- **type for unions and utilities** — Use type for union types, utility types, and function signatures — things that are not directly 'objects with fields'.
- **Never use any** — any disables type checking completely. Use unknown and narrow it with type guards instead.
- **strict: true in tsconfig** — Strict mode enables the full set of type checks. Without it, TypeScript catches only the most obvious errors.

**Pattern** — `components/UserCard.tsx — typed component`

```
import type { ReactNode } from 'react';

// ✅ interface for component props
interface UserCardProps {
  name: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';  // union type
  isActive: boolean;
  onEdit: (id: string) => void;          // typed event handler
  children?: ReactNode;
}

// ✅ typed event handler
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
  event.preventDefault();
}

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

// ❌ never do this
const fetchUser = async (): Promise<any> => { ... }

// ✅ use unknown and narrow
const fetchUser = async (): Promise<unknown> => { ... }
```

**Implementation — Next.js**

Next.js page components receive typed params and searchParams. Always type these explicitly. URL params are always strings — convert to the expected type before use.

File: `app/users/[id]/page.tsx`

```tsx
// ✅ Typed Next.js page props
interface PageProps {
  params: Promise<{ id: string }>;
  searchParams: Promise<{ tab?: string }>;
}

export default async function UserPage({ params }: PageProps) {
  const { id } = await params;

  // URL params are always strings — convert to number before passing to the hook
  return <UserDetail id={Number(id)} />;
}

// ✅ Typed Server Action
async function updateUser(
  id: string,
  data: UpdateUserInput
): Promise<{ success: boolean }> {
  'use server';
  // ...
}
```

**Implementation — Vite**

In Vite + React Router, type your route params using useParams with a generic.

File: `features/users/components/UserDetail.tsx`

```tsx
import { useParams } from 'react-router-dom';

// ✅ Typed route params
function UserDetail() {
  const { id } = useParams<{ id: string }>();

  // id is string | undefined — handle both cases
  if (!id) return null;

  return <div>{id}</div>;
}

// ✅ Typed custom hook return
function useUser(id: string): {
  user: User | null;
  isLoading: boolean;
  error: Error | null;
} {
  // ...
}
```

Read more: https://reactprinciples.dev/cookbook/typescript-for-react

---

Source: https://reactprinciples.dev/nextjs/cookbook/typescript-for-react
All recipes (compact): https://reactprinciples.dev/llms.txt
All recipes (full): https://reactprinciples.dev/llms-full.txt
