# TypeScript Best Practices for React
TypeScript has become the standard for building robust React applications. Here are essential patterns and practices to write better TypeScript React code.
## Component Props Typing
### Interface vs Type
Use interfaces for component props as they're more extensible:
```tsx
// ✅ Good - Use interface for props
interface ButtonProps {
children: React.ReactNode
variant?: 'primary' | 'secondary'
onClick?: () => void
}
// ❌ Avoid - Type alias for props
type ButtonProps = {
children: React.ReactNode
variant?: 'primary' | 'secondary'
onClick?: () => void
}
```
### Extending HTML Elements
Extend native HTML element props when building wrapper components:
```tsx
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary'
}
const Button: React.FC<ButtonProps> = ({ variant = 'primary', ...props }) => {
return <button className={`btn btn-${variant}`} {...props} />
}
```
## Hooks Typing
### useState with Complex Types
Be explicit with complex state types:
```tsx
interface User {
id: string
name: string
email: string
}
// ✅ Explicit typing
const [user, setUser] = useState<User | null>(null)
// ✅ With initial value
const [users, setUsers] = useState<User[]>([])
```
### Custom Hooks
Always type custom hook returns:
```tsx
interface UseApiResult<T> {
data: T | null
loading: boolean
error: string | null
}
function useApi<T>(url: string): UseApiResult<T> {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
// ... implementation
return { data, loading, error }
}
```
## Event Handling
Type event handlers properly:
```tsx
interface FormProps {
onSubmit: (data: FormData) => void
}
const Form: React.FC<FormProps> = ({ onSubmit }) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
onSubmit(formData)
}
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleInputChange} />
</form>
)
}
```
## Generic Components
Create reusable components with generics:
```tsx
interface ListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
keyExtractor: (item: T) => string
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
)
}
// Usage
<List
items={users}
renderItem={user => <span>{user.name}</span>}
keyExtractor={user => user.id}
/>
```
## Utility Types
Leverage TypeScript's utility types:
```tsx
interface User {
id: string
name: string
email: string
password: string
}
// Pick only needed properties
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// Omit sensitive properties
type UserInput = Omit<User, 'id'>
// Make all properties optional
type PartialUser = Partial<User>
// Make specific properties optional
type UserUpdate = Partial<Pick<User, 'name' | 'email'>> & Pick<User, 'id'>
```
## Strict TypeScript Configuration
Use strict TypeScript settings in your `tsconfig.json`:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true
}
}
```
Following these practices will make your React TypeScript code more maintainable, type-safe, and developer-friendly!