TypeScript with React
TypeScript adds static types to React, catching errors before runtime and improving developer experience.
Setup
bash
1# New Vite project with TypeScript
2npm create vite@latest my-app -- --template react-ts
3
4# Add TypeScript to existing project
5npm install -D typescript @types/react @types/react-domTyping Components
Function Components
tsx
1// Inline props type
2function Greeting({ name }: { name: string }) {
3 return <h1>Hello, {name}!</h1>
4}
5
6// Interface for props
7interface ButtonProps {
8 label: string
9 onClick: () => void
10 disabled?: boolean // Optional prop
11}
12
13function Button({ label, onClick, disabled = false }: ButtonProps) {
14 return (
15 <button onClick={onClick} disabled={disabled}>
16 {label}
17 </button>
18 )
19}
20
21// Type alias (alternative to interface)
22type CardProps = {
23 title: string
24 children: React.ReactNode
25}
26
27function Card({ title, children }: CardProps) {
28 return (
29 <div className="card">
30 <h2>{title}</h2>
31 {children}
32 </div>
33 )
34}Children Types
tsx
1// ReactNode - most flexible
2interface LayoutProps {
3 children: React.ReactNode // Accepts anything renderable
4}
5
6// ReactElement - only JSX elements
7interface WrapperProps {
8 children: React.ReactElement
9}
10
11// String only
12interface TitleProps {
13 children: string
14}
15
16// Function children (render props)
17interface MouseProps {
18 children: (position: { x: number; y: number }) => React.ReactNode
19}Typing Hooks
useState
tsx
1// Type is inferred
2const [count, setCount] = useState(0) // number
3const [name, setName] = useState('') // string
4
5// Explicit type for complex types
6const [user, setUser] = useState<User | null>(null)
7const [items, setItems] = useState<Item[]>([])
8
9// Union type for specific values
10type Status = 'idle' | 'loading' | 'success' | 'error'
11const [status, setStatus] = useState<Status>('idle')useRef
tsx
1// DOM element ref
2const inputRef = useRef<HTMLInputElement>(null)
3const divRef = useRef<HTMLDivElement>(null)
4const buttonRef = useRef<HTMLButtonElement>(null)
5
6// Usage
7const focus = () => {
8 inputRef.current?.focus()
9}
10
11// Mutable ref (for values)
12const countRef = useRef<number>(0)
13countRef.current = 5 // No erroruseReducer
tsx
1type State = {
2 count: number
3 step: number
4}
5
6type Action =
7 | { type: 'increment' }
8 | { type: 'decrement' }
9 | { type: 'setStep'; payload: number }
10 | { type: 'reset' }
11
12function reducer(state: State, action: Action): State {
13 switch (action.type) {
14 case 'increment':
15 return { ...state, count: state.count + state.step }
16 case 'decrement':
17 return { ...state, count: state.count - state.step }
18 case 'setStep':
19 return { ...state, step: action.payload }
20 case 'reset':
21 return { count: 0, step: 1 }
22 }
23}
24
25const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 })useContext
tsx
1interface AuthContextType {
2 user: User | null
3 login: (email: string, password: string) => Promise<void>
4 logout: () => void
5}
6
7const AuthContext = createContext<AuthContextType | null>(null)
8
9function useAuth(): AuthContextType {
10 const context = useContext(AuthContext)
11 if (!context) {
12 throw new Error('useAuth must be used within AuthProvider')
13 }
14 return context
15}Typing Events
tsx
1function Form() {
2 // Input change event
3 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
4 console.log(e.target.value)
5 }
6
7 // Form submit event
8 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
9 e.preventDefault()
10 }
11
12 // Click event
13 const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
14 console.log(e.clientX, e.clientY)
15 }
16
17 // Keyboard event
18 const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
19 if (e.key === 'Enter') {
20 console.log('Enter pressed')
21 }
22 }
23
24 return (
25 <form onSubmit={handleSubmit}>
26 <input onChange={handleChange} onKeyDown={handleKeyDown} />
27 <button onClick={handleClick}>Submit</button>
28 </form>
29 )
30}Generic Components
tsx
1// Generic list component
2interface ListProps<T> {
3 items: T[]
4 renderItem: (item: T) => React.ReactNode
5 keyExtractor: (item: T) => string
6}
7
8function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
9 return (
10 <ul>
11 {items.map(item => (
12 <li key={keyExtractor(item)}>{renderItem(item)}</li>
13 ))}
14 </ul>
15 )
16}
17
18// Usage
19interface User {
20 id: string
21 name: string
22}
23
24<List<User>
25 items={users}
26 renderItem={(user) => <span>{user.name}</span>}
27 keyExtractor={(user) => user.id}
28/>Extending HTML Elements
tsx
1// Extend button props
2interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
3 variant?: 'primary' | 'secondary'
4 size?: 'small' | 'medium' | 'large'
5}
6
7function Button({ variant = 'primary', size = 'medium', ...props }: ButtonProps) {
8 return (
9 <button
10 className={`btn btn-${variant} btn-${size}`}
11 {...props}
12 />
13 )
14}
15
16// Extend input props
17interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
18 label?: string
19 error?: string
20}
21
22function Input({ label, error, ...props }: InputProps) {
23 return (
24 <div>
25 {label && <label>{label}</label>}
26 <input {...props} />
27 {error && <span className="error">{error}</span>}
28 </div>
29 )
30}Type Utilities
tsx
1// Pick - select specific props
2type ButtonLabelProps = Pick<ButtonProps, 'label' | 'disabled'>
3
4// Omit - exclude specific props
5type ButtonWithoutLabel = Omit<ButtonProps, 'label'>
6
7// Partial - make all props optional
8type OptionalButtonProps = Partial<ButtonProps>
9
10// Required - make all props required
11type RequiredButtonProps = Required<ButtonProps>
12
13// Extract return type of component
14type Props = React.ComponentProps<typeof Button>TypeScript makes React development safer and more productive!
