React with TypeScript
Why TypeScript with React?
TypeScript adds static typing to React, catching errors at compile time rather than runtime. It provides excellent IDE support, makes refactoring safer, and serves as living documentation for your components.
// TypedComponents.tsx
import { ReactNode, MouseEvent, ChangeEvent, FC } from 'react'
// Interface for props
interface ButtonProps {
label: string
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
onClick?: (event: MouseEvent<HTMLButtonElement>) => void
children?: ReactNode
}
// Typed function component
const Button: FC<ButtonProps> = ({
label,
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
onClick,
children
}) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled || loading}
onClick={onClick}
>
{loading ? 'Loading...' : (children || label)}
</button>
)
}
// Typed input component
interface InputProps {
label: string
value: string
onChange: (value: string) => void
type?: 'text' | 'email' | 'password' | 'number'
error?: string
required?: boolean
}
function Input({ label, value, onChange, type = 'text', error, required }: InputProps) {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value)
}
return (
<div>
<label>{label}{required && ' *'}</label>
<input type={type} value={value} onChange={handleChange} />
{error && <span className="error">{error}</span>}
</div>
)
}
// Generic component
interface ListProps<T> {
items: T[]
renderItem: (item: T, index: number) => ReactNode
keyExtractor: (item: T) => string | number
emptyMessage?: string
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage = 'No items' }: ListProps<T>) {
if (items.length === 0) return <p>{emptyMessage}</p>
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
)
}
// Usage
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
<List
items={users}
keyExtractor={u => u.id}
renderItem={u => <span>{u.name}</span>}
/>
// TypedHooks.tsx
import { useState, useRef, useEffect, useCallback } from 'react'
// Typed useState
const [count, setCount] = useState<number>(0)
const [user, setUser] = useState<User | null>(null)
const [items, setItems] = useState<string[]>([])
// Typed useRef
const inputRef = useRef<HTMLInputElement>(null)
const timerRef = useRef<ReturnType<typeof setTimeout>>(null)
// Access DOM element
inputRef.current?.focus()
// Typed useCallback
const handleClick = useCallback((id: number): void => {
setItems(prev => prev.filter((_, i) => i !== id))
}, [])
// Typed custom hook
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch(url)
.then(res => res.json() as Promise<T>)
.then(data => { setData(data); setLoading(false) })
.catch(err => { setError(err.message); setLoading(false) })
}, [url])
return { data, loading, error }
}
// Usage with type inference
const { data: users } = useFetch<User[]>('/api/users')
// users is typed as User[] | null
// Typed event handlers
function handleInputChange(e: React.ChangeEvent<HTMLInputElement>): void {
console.log(e.target.value)
}
function handleFormSubmit(e: React.FormEvent<HTMLFormElement>): void {
e.preventDefault()
}
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {
if (e.key === 'Enter') console.log('Enter pressed')
}
function handleMouseEnter(e: React.MouseEvent<HTMLDivElement>): void {
console.log(e.clientX, e.clientY)
}
// types.ts — shared type definitions
export type UserRole = 'admin' | 'user' | 'moderator'
export interface User {
id: number
name: string
email: string
role: UserRole
avatar?: string
createdAt: string
isActive: boolean
}
export interface ApiResponse<T> {
data: T
message: string
success: boolean
pagination?: Pagination
}
export interface Pagination {
page: number
perPage: number
total: number
totalPages: number
}
// Utility types
export type CreateUser = Omit<User, 'id' | 'createdAt'>
export type UpdateUser = Partial<Omit<User, 'id'>>
export type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>
// Component prop types
export interface TableColumn<T> {
key: keyof T
header: string
sortable?: boolean
render?: (value: T[keyof T], row: T) => React.ReactNode
}
// Hook return types
export interface UseFormReturn<T> {
values: T
errors: Partial<Record<keyof T, string>>
handleChange: (field: keyof T, value: T[keyof T]) => void
handleSubmit: (onSubmit: (values: T) => void) => (e: React.FormEvent) => void
reset: () => void
isValid: boolean
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.