TypeScript for React

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

auto_awesome

Use with AI

All AI integrationsarrow_forward
Install skills:npx skills add sindev08/react-principles-skills
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_circle
    interface for component propsUse interface to define component props. It is extendable and reads clearly as a contract.
  • check_circle
    type for unions and utilitiesUse type for union types, utility types, and function signatures — things that are not directly 'objects with fields'.
  • check_circle
    Never use anyany disables type checking completely. Use unknown and narrow it with type guards instead.
  • check_circle
    strict: 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. URL params are always strings — convert to the expected type before use.

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;

  // 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';
  // ...
}
open_in_new

View TypeScript config in starter

View the real implementation in react-principles-nextjs

arrow_forward
menu_book
React Principles

Helping developers build robust React applications since 2026.

© 2026 React Principles. Built with ❤️ for the community.
React Principles