Introduction
TypeScript transforms React development by catching prop-related bugs at compile time. Properly typed props provide autocomplete, documentation, and safety.
Key Concepts
Props typing in React uses TypeScript types (preferred over interfaces per modern conventions) to define the shape of data a component accepts.
tsxtype ButtonProps = { label: string; onClick: () => void; disabled?: boolean; };
Real World Context
In production codebases:
- Teams catch prop mismatches during development, not in production
- IDE autocomplete makes components self-documenting
- Refactoring is safer with compile-time checks
- Onboarding is faster when types explain the API
Deep Dive
Basic Props Typing
tsxtype UserCardProps = { name: string; email: string; avatar?: string; // Optional prop role: 'admin' | 'user' | 'guest'; // Union type }; function UserCard({ name, email, avatar, role }: UserCardProps) { return ( <div className="user-card"> {avatar && <img src={avatar} alt={name} />} <h3>{name}</h3> <p>{email}</p> <span className={`badge-${role}`}>{role}</span> </div> ); }
Children Prop
Use ReactNode for the children prop:
tsxtype CardProps = { title: string; children: ReactNode; // Any valid JSX }; function Card({ title, children }: CardProps) { return ( <div className="card"> <h2>{title}</h2> {children} </div> ); }
Extending HTML Element Props
tsximport { ComponentProps } from 'react'; // Extend button props type ButtonProps = ComponentProps<'button'> & { variant?: 'primary' | 'secondary'; isLoading?: boolean; }; function Button({ variant = 'primary', isLoading, children, ...props }: ButtonProps) { return ( <button className={`btn btn-${variant}`} disabled={isLoading || props.disabled} {...props} > {isLoading ? 'Loading...' : children} </button> ); }
Default Props with Destructuring
tsxtype PaginationProps = { totalPages: number; currentPage?: number; onPageChange: (page: number) => void; }; function Pagination({ totalPages, currentPage = 1, // Default value onPageChange }: PaginationProps) { // ... }
Common Pitfalls
- Using
anyfor props: Defeats the purpose of TypeScript. Always define explicit types. - Overusing interfaces: Types are more flexible for unions and intersections. Use types unless you need declaration merging.
- Forgetting optional markers: Required props without
?cause errors when omitted.
Best Practices
- Use
typeoverinterfacefor props (per user rules and modern conventions) - Export prop types for consumers:
export type ButtonProps = {...} - Use
ComponentProps<'element'>to extend native element props - Provide sensible defaults with destructuring
- Use union types for constrained string values instead of plain
string - Add JSDoc comments for complex props
Summary
Typing props catches bugs early and improves DX. Use TypeScript types, extend native element props when needed, and leverage union types for constrained values.
Code Examples
tsx
import { ReactNode, ComponentProps } from 'react';
// Basic props with union types
type AlertProps = {
type: 'success' | 'error' | 'warning' | 'info';
title: string;
message: string;
onDismiss?: () => void;
};
function Alert({ type, title, message, onDismiss }: AlertProps) {
return (
<div className={`alert alert-${type}`} role="alert">
<strong>{title}</strong>
<p>{message}</p>
{onDismiss && (
<button onClick={onDismiss} aria-label="Dismiss">
×
</button>
)}
</div>
);
}
// Extending HTML element props
type InputProps = ComponentProps<'input'> & {
label: string;
error?: string;
};
function Input({ label, error, id, ...inputProps }: InputProps) {
const inputId = id || `input-${label.toLowerCase().replace(/\s/g, '-')}`;
return (
<div className="form-field">
<label htmlFor={inputId}>{label}</label>
<input
id={inputId}
aria-invalid={!!error}
aria-describedby={error ? `${inputId}-error` : undefined}
{...inputProps}
/>
{error && (
<span id={`${inputId}-error`} className="error">
{error}
</span>
)}
</div>
);
}
// Usage with full type safety
<Alert type="success" title="Saved" message="Your changes have been saved" />
<Input label="Email" type="email" required placeholder="you@example.com" />