Skip to main content
Back to Blog
TypeScriptReactJavaScriptBest Practices

TypeScript Best Practices for React Developers - Debmalya Biswas

DB
Debmalya Biswas
Frontend SDE
9 min read
TypeScript Best Practices for React Developers - Debmalya Biswas

TypeScript Best Practices for React Developers

Hi, I'm Debmalya Biswas, a frontend developer who's passionate about type safety and developer experience. Today, I'm sharing TypeScript best practices I use in production React applications.

Why TypeScript?

As Debmalya Biswas, SDE, I've found TypeScript invaluable for:

- Catching bugs at compile time

- Improving code documentation

- Enhancing IDE autocomplete

- Facilitating refactoring

Essential Type Patterns

Component Props Typing

// Basic props interface
interface ButtonProps {
  label: string
  onClick: () => void
  variant?: 'primary' | 'secondary'
  disabled?: boolean
}

export function Button({ label, onClick, variant = 'primary', disabled }: ButtonProps) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {label}
    </button>
  )
}

Children Props

import type { ReactNode } from 'react'

interface ContainerProps {
  children: ReactNode
  className?: string
}

export function Container({ children, className }: ContainerProps) {
  return <div className={className}>{children}</div>
}

Event Handlers

interface FormProps {
  onSubmit: (data: FormData) => void
}

function MyForm({ onSubmit }: FormProps) {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    // Handle form submission
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // Handle input change
  }

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    // Handle button click
  }

  return <form onSubmit={handleSubmit}>{/* Form fields */}</form>
}

Advanced Patterns

Generic Components

In the Debmalya Biswas portfolio, I use generic components for flexibility:

interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => 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
const users = [{ id: '1', name: 'Debmalya' }]
<List
  items={users}
  renderItem={user => <span>{user.name}</span>}
  keyExtractor={user => user.id}
/>

Discriminated Unions

type LoadingState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: any }
  | { status: 'error'; error: Error }

function DataDisplay({ state }: { state: LoadingState }) {
  switch (state.status) {
    case 'idle':
      return <div>Click to load</div>
    case 'loading':
      return <div>Loading...</div>
    case 'success':
      return <div>{state.data}</div> // TypeScript knows data exists
    case 'error':
      return <div>Error: {state.error.message}</div>
  }
}

Utility Types

// Pick specific properties
type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>

// Omit properties
type UserWithoutPassword = Omit<User, 'password'>

// Partial for optional updates
type UserUpdate = Partial<User>

// Required makes all properties required
type RequiredUser = Required<Partial<User>>

// Record for key-value mappings
type ErrorMessages = Record<string, string>

Hooks with TypeScript

useState

// Type inference
const [count, setCount] = useState(0) // inferred as number

// Explicit typing
const [user, setUser] = useState<User | null>(null)

// With initial value
const [items, setItems] = useState<Item[]>([])

useRef

// DOM refs
const inputRef = useRef<HTMLInputElement>(null)

// Mutable value refs
const timerRef = useRef<NodeJS.Timeout | null>(null)

useReducer

type State = { count: number }
type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'set'; value: number }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'set':
      return { count: action.value }
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0 })

Custom Hooks

As Debmalya Biswas, frontend developer, I create typed custom hooks:

function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch {
      return initialValue
    }
  })

  const setValue = (value: T) => {
    setStoredValue(value)
    window.localStorage.setItem(key, JSON.stringify(value))
  }

  return [storedValue, setValue]
}

API Integration

Typed Fetch Wrapper

async function fetchApi<T>(url: string): Promise<T> {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error('Network response was not ok')
  }
  return response.json()
}

// Usage
const user = await fetchApi<User>('/api/user')

Form Data Handling

interface LoginForm {
  email: string
  password: string
}

function LoginForm() {
  const [formData, setFormData] = useState<LoginForm>({
    email: '',
    password: ''
  })

  const handleChange = (field: keyof LoginForm) =>
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setFormData(prev => ({ ...prev, [field]: e.target.value }))
    }

  return (
    <form>
      <input value={formData.email} onChange={handleChange('email')} />
      <input value={formData.password} onChange={handleChange('password')} />
    </form>
  )
}

Best Practices

1. Avoid &#039;any&#039;

// Bad
function process(data: any) { }

// Good
function process(data: unknown) {
  if (typeof data === 'string') {
    // TypeScript knows data is string here
  }
}

2. Use Type Inference

// Let TypeScript infer when obvious
const name = 'Debmalya Biswas' // string inferred

// Specify types when needed
const config: AppConfig = getConfig()

3. Prefer Interfaces for Objects

// Good for objects
interface User {
  id: string
  name: string
}

// Good for unions/primitives
type ID = string | number
type Status = 'active' | 'inactive'

4. Use Const Assertions

const routes = {
  home: '/',
  blog: '/blog',
  about: '/about'
} as const

type Route = typeof routes[keyof typeof routes]

Conclusion

TypeScript significantly improves React development. These patterns power the Debmalya Biswas website and ensure type safety across all components.

Keep coding with confidence!

*Debmalya Biswas is a frontend SDE specializing in TypeScript, React, and type-safe web development. Visit the Debmalya Biswas portfolio for more insights.*

DB

About Debmalya Biswas

Debmalya Biswas is a passionate frontend developer and software development engineer (SDE) specializing in React, Next.js, TypeScript, and modern web technologies. With extensive experience building performant, accessible web applications, Debmalya focuses on creating exceptional user experiences.

View Portfolio →

More Articles