TypeScript for React
How to type component props, event handlers, and hooks correctly. The contracts that prevent silent bugs.
01
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.
lightbulb
Start by typing your component props. If you can describe what a component accepts and returns, the rest of the types follow naturally.
02
Rules
- check_circleinterface for component propsUse interface to define component props. It is extendable and reads clearly as a contract.
- check_circletype for unions and utilitiesUse type for union types, utility types, and function signatures — things that are not directly 'objects with fields'.
- check_circleNever use anyany disables type checking completely. Use unknown and narrow it with type guards instead.
- check_circlestrict: true in tsconfigStrict mode enables the full set of type checks. Without it, TypeScript catches only the most obvious errors.
03
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> => { ... }
04
Implementation
info
Version Compatibility
Requires React 19+ and the latest stable versions of all dependencies shown.
Next.js page components receive typed params and searchParams. Always type these explicitly.
app/users/[id]/page.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; return <UserDetail id={id} />; } // ✅ Typed Server Action async function updateUser( id: string, data: UpdateUserInput ): Promise<{ success: boolean }> { 'use server'; // ... }