John Andrew Batulan
John Andrew Batulan
TypeScript
React
Best Practices

TypeScript Best Practices for React

3/5/2024
12 min read
by Mike Johnson
# 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!