Skip
Arish's avatar

33. TypeScript with React


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-dom

Typing 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 error

useReducer

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!