TypeScript dengan React — React

JavaScript dynamic typing kasih kebebasan, tapi juga sumber bug runtime. Fungsi terima string, dipanggil dengan number — gak ada yang protes sampai user klik da

JavaScript dynamic typing kasih kebebasan, tapi juga sumber bug runtime. Fungsi terima string, dipanggil dengan number — gak ada yang protes sampai user klik dan crash. Pas project besar (10+ developer, 100+ component, ratusan endpoint API), bug type setting ngebakar productivitas. TypeScript = JavaScript + static type checking. Tulis kode dengan type annotations, IDE catch error sebelum kamu push commit, refactor jadi safer (rename variable: tools tahu di mana saja referensi-nya). React + TypeScript = 2026 default modern di kebanyakan project production.

💡 Investasi yang berbayar. Belajar TypeScript butuh 2-3 bulan ekstra effort. Tapi setelah produktif, kamu gak akan mau balik ke vanilla JavaScript untuk project serius. Auto-complete props lebih cepat, refactor PD, runtime error berkurang drastis. Skill TS = lowongan kerja Indonesia naik premium 30-40%.

Props dengan Interface/Type

interface UserCardProps {
  nama: string
  umur: number
  isAdmin?: boolean    // opsional
  onClick?: () => void
}

function UserCard({ nama, umur, isAdmin = false, onClick }: UserCardProps) {
  return <div onClick={onClick}>{nama} ({umur})</div>
}

type vs interface — di kebanyakan kasus interchangeable. Convention modern: type untuk props (lebih flexible), interface untuk public API (bisa di-extend).

State dengan Generics

const [count, setCount] = useState<number>(0)        // explicit
const [count, setCount] = useState(0)                 // inferred → number
const [user, setUser] = useState<User | null>(null)   // union
const [items, setItems] = useState<string[]>([])      // array
const [data, setData] = useState<{ id: number, name: string }[]>([])

TypeScript infer dari initial value — gak perlu explicit kalau initial type clear.

Event Types — Tau Mana Yang Mana

// Mouse events
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget.disabled)  // type-safe
}

// Change events
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value)
}

// Form submit
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault()
}

// Keyboard
const handleKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
  if (e.key === "Enter") submitForm()
}

Inline arrow di JSX biasanya inferred:

<button onClick={(e) => /* e is React.MouseEvent<HTMLButtonElement> */} />

Children Type

interface LayoutProps {
  children: React.ReactNode    // ← pakai ini, paling permissive (string, JSX, array, null)
  title: string
}

// Alternatif:
// React.ReactElement — JSX only
// React.ReactChild — string atau JSX
// JSX.Element — single JSX (bukan fragment)

Custom Hook Type

function useToggle(initial = false): [boolean, () => void] {
  const [value, setValue] = useState(initial)
  const toggle = () => setValue(v => !v)
  return [value, toggle]
}

// Atau pakai object return (preferred modern)
function useToggle(initial = false) {
  const [value, setValue] = useState(initial)
  const toggle = () => setValue(v => !v)
  return { value, toggle }
}

Modern style: object return, type inferred otomatis, gak perlu manual annotation.

Hindari React.FC<Props>

// ❌ Pattern lama, gak direkomendasi lagi
const Button: React.FC<Props> = ({ label }) => <button>{label}</button>

// ✓ Modern style
function Button({ label }: Props) {
  return <button>{label}</button>
}

Tim React resmi gak rekomendasi React.FC lagi — gak kasih type safety extra, gak intuitif untuk junior, return type bisa di-infer dari function biasa.

Generic Components

interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => React.ReactNode
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}</ul>
}

// Pemakaian — type T di-infer dari items
<List items={users} renderItem={(user) => user.name} />
//          ^User[]            ^user: User

Discriminated Union — Pattern Powerful

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

function UserList({ state }: { state: FetchState<User[]> }) {
  if (state.status === "loading") return <Spinner />
  if (state.status === "error") return <Error message={state.error} />
  // di sini TypeScript TAU state.data ada (narrowing)
  return <ul>{state.data.map(...)}</ul>
}

🎭 Analogi sehari-hari

TypeScript itu kayak kontrak kerja yang jelas. JavaScript = kerja informal — "udah deh, garap dulu", deadline molor, output beda dari ekspektasi, harus ulang dari awal. TypeScript = kontrak hitam-putih — spec dijelasin di awal (interface props), output harus match (return type), kalau ada perubahan harus diskusi di awal (refactor). Setup-nya lebih lama (belajar syntax, define types), tapi pas project udah jalan, sebagian besar miscommunication langsung kelihatan di IDE merah — kamu fix sebelum jadi bug production. Tim besar (10+ orang) di Tokopedia/Gojek tanpa TypeScript = chaos. Project pribadi quick prototype OK pake JS.

⚠️ Jebakan yang sering ditemui

🎯 Type props — pilih type atau interface?

  • Component propstype (lebih flexible, support union, intersection, conditional)
  • Public API libraryinterface (bisa di-extend di consumer side)
  • Function signature, callback typetype
  • Object shape sederhana → keduanya OK, pilih konsisten dalam project

Aturan project hypersheets-frontend: pakai type untuk props default, interface saat butuh declaration merging.

TL;DR: TypeScript = JavaScript + static type check, catch bug sebelum runtime. Stack modern React 2026: TS default. Props pakai type/interface, state useState<T>(...), event React.ChangeEvent<HTMLInputElement>, children React.ReactNode. Hindari React.FC (pattern lama), any (defeats purpose), as casting tanpa cek. Generic components untuk reusable like <List<T>>. Discriminated union powerful untuk state machine pattern.

Yang akan kamu pelajari