Practical TypeScript patterns for React — from component props to event handlers to generic components.
TypeScript Makes React Better
TypeScript catches prop errors at build time, provides autocompletion in your editor, and documents component APIs automatically. The investment pays off quickly.
Component Props
Define props with an interface or type:
interface ButtonProps {
label: string
variant?: 'primary' | 'secondary'
onClick: () => void
}
function Button({ label, variant = 'primary', onClick }: ButtonProps) { return }
The question mark makes variant optional with a default value.
Children Prop
interface CardProps {
children: React.ReactNode
className?: string
}
React.ReactNode accepts any renderable content — strings, elements, arrays, fragments.
Event Handlers
Use the specific event type instead of any:
function SearchInput() {
const handleChange = (e: React.ChangeEvent) => {
console.log(e.target.value)
}
return
}
Common types: ChangeEvent, MouseEvent, KeyboardEvent, FormEvent.
Extending HTML Elements
When your component wraps a native HTML element, extend its props:
interface InputProps extends React.InputHTMLAttributes {
label: string
}
Now your component accepts all native input attributes plus your custom ones.
Generic Components
For components that work with different data types:
interface ListProps {
items: T[]
renderItem: (item: T) => React.ReactNode
}
function List({ items, renderItem }: ListProps) { return {items.map(renderItem)}
}
TypeScript infers T from usage — no manual type annotation needed at the call site.
State Types
Usually TypeScript infers state types from the initial value. Specify only when the initial value does not represent all possible states:
const [user, setUser] = useState(null)
Discriminated Unions for Component States
type State =
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'success'; data: Product[] }
TypeScript narrows the type when you check status, preventing you from accessing data on an error state.
Avoid These
any— defeats the purpose of TypeScript- Overly complex generic types — keep it readable
- Type assertions (
as) — usually a sign you need to fix the actual type - Exporting every type — only export what consumers need