TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

Turn AI service development and operations into one improvement loop

Aidevops.kr covers LLMOps, RAG, agents, observability, evaluation, and cost-performance optimization for production AI services.

React + TypeScript Design Guide

· Updated Apr 16
React + TypeScript Design Guide diagram
Visual guide to the key flow, architecture, and decision points covered in this post.
The real advantage of React with TypeScript is not just catching errors early. It is making component contracts explicit. When types reveal what props a component accepts, what events happen, and which states are possible, the cost of change falls across the whole team. If the types are complicated but the intent is still unclear, productivity usually drops instead.

Start simple

Today, it is easy to create a React + TypeScript project with a Vite template.

npm create vite@latest my-app -- --template react-ts

At the start, what matters is less the setup itself and more the rules the team will share: enable strict, minimize any, and separate domain models from UI models.

Prop types are the component contract

Prop types act as documentation for reuse. It helps to separate required values from optional ones clearly and use names that make intent obvious.

interface ButtonProps {
  variant?: 'primary' | 'secondary'
  disabled?: boolean
  onClick?: () => void
  children: React.ReactNode
}

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

Unions are stronger than stringly state

If loading, success, and failure are modeled with several booleans, it becomes easy to represent invalid combinations. In TypeScript, it is safer to model branching state with a union.

type FetchState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string }

This works well with render branches and reduces impossible state combinations.

Be explicit about event types only where it helps

Beginners often try to write every event type by hand. In real projects, it is usually better to lean on IDE inference. But handlers that are extracted or reused tend to read more clearly with explicit types.

Use generics only at real reuse boundaries

Once you have TypeScript, it is tempting to build generic components early. In practice, it is better to wait until a pattern repeats at least two or three times. Generalizing too early often hurts usability.

Common mistakes

One of the most common mistakes is spreading backend DTOs directly through UI components. When the API response shape changes, the entire screen layer shakes. Another common issue is overusing React.FC or escaping with any, which weakens trust in the type system.

Wrap-up

The core of React + TypeScript is not advanced type wizardry, but clear contracts. When props, state, events, and data models are separated appropriately, types stop being a burden and become interfaces that make the codebase more resilient to change.

What Gets Hard in Production

  • Type drift starts when API DTOs, form models, and view props all reuse the same interface.
  • Compile-time safety drops quickly if the team treats any as a shortcut instead of a last resort.
  • Shared utility types become a maintenance burden when they hide business meaning behind clever abstractions.

Architecture Decisions That Matter

  • Turn on strict, noUncheckedIndexedAccess, and path alias rules early so bad patterns do not become default.
  • Separate domain types, server response types, and UI state types instead of forcing one universal model.
  • Prefer explicit module boundaries over a giant types.ts file that every screen imports.

Practical Example

A stable pattern is to map transport types into UI-friendly models at the boundary:

type UserResponse = {
  id: string
  full_name: string
  last_login_at: string | null
}

type UserCardModel = {
  id: string
  displayName: string
  isDormant: boolean
}

function toUserCardModel(user: UserResponse): UserCardModel {
  return {
    id: user.id,
    displayName: user.full_name,
    isDormant: user.last_login_at === null,
  }
}

Anti-Patterns to Avoid

  • Exporting backend response shapes directly into reusable components.
  • Building generic helpers before the repetition is real and measured.
  • Using type assertions to silence warnings that are actually modeling problems.

Operational Checklist

  • Review tsconfig changes as architecture decisions, not formatting trivia.
  • Keep ESLint rules aligned with the TypeScript strictness level.
  • Require explicit typing at public boundaries such as hooks, context values, and component props.
  • Watch incremental build time as the type graph grows.

Final Judgment

React and TypeScript become a flagship combination when types describe real boundaries. If types only mirror implementation details, the team pays the complexity cost without gaining safety.

Continue Reading

Related posts

Next Path

Keep exploring this topic as a system