Type Event onBlur React di TypeScript

4 menit baca.
Tags: ReactTypeScript

Type TypeScript untuk event onBlur di React adalah React.FocusEvent<T>, dengan T adalah elemen yang punya handler-nya. Untuk input, artinya React.FocusEvent<HTMLInputElement>.

Jawaban cepat

const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
  console.log(e.target.value)
}

<input onBlur={handleBlur} />

Atau beri type ke seluruh function dengan handler type:

const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
  console.log(e.target.value)
}

Keduanya hasilnya sama. Cara kedua praktis kalau handler-nya dioper sebagai prop, karena onBlur?: React.FocusEventHandler<HTMLInputElement> lebih enak dibaca daripada menulis signature function lengkap.

onFocus memakai type yang sama.

Handler inline tidak perlu type

Kalau handler ditulis inline, TypeScript menyimpulkan type event dari elemennya:

<input onBlur={(e) => console.log(e.target.value)} />

Di sini e sudah bertipe React.FocusEvent<HTMLInputElement>. Type eksplisit hanya perlu kalau handler-nya function terpisah.

Pilih type elemen yang benar

Generic menentukan apa isi e.target. Kalau salah, .value hilang:

const handleBlur = (e: React.FocusEvent) => {
  console.log(e.target.value)
}
error TS2339: Property 'value' does not exist on type 'EventTarget & Element'.

Tanpa generic, T default-nya Element, yang tidak punya value. Hal yang sama terjadi dengan React.FocusEvent<HTMLDivElement>, karena div juga tidak punya value.

Pakai HTMLTextAreaElement untuk textarea dan HTMLSelectElement untuk select. Kalau satu handler dipakai beberapa field, pakai union:

const handleBlur = (
  e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
) => {
  validateField(e.target.name, e.target.value)
}

<input name="email" onBlur={handleBlur} />
<textarea name="bio" onBlur={handleBlur} />
<select name="country" onBlur={handleBlur} />

name dan value ada di ketiganya, jadi union ini lolos compile.

Type event yang salah tetap bisa lolos compile

Kamu mungkin mengira TypeScript menolak handler ChangeEvent di onBlur. Ternyata tidak:

const handleBlur = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value)
}

<input onBlur={handleBlur} /> // no error

Dengan @types/react 19.3 dan strict: true, kode ini lolos compile. ChangeEvent<T> hanya menambahkan target bertipe T di atas synthetic event dasar, sedangkan FocusEvent<T> punya target yang sama plus relatedTarget. Function yang menerima type lebih kecil aman menerima type yang lebih besar, jadi TypeScript mengizinkannya. Di runtime juga jalan, karena kamu hanya membaca target.value. Tetap pakai FocusEvent, karena itu type yang benar-benar dikirim React dan memberi kamu relatedTarget.

Jangan pakai FocusEvent milik DOM

Menulis FocusEvent tanpa prefix React. memberi kamu type event native browser, dan yang ini ditolak:

const handleBlur = (e: FocusEvent) => {}

<input onBlur={handleBlur} />
error TS2322: Type '(e: FocusEvent) => void' is not assignable to type 'FocusEventHandler<HTMLInputElement>'.
  Types of parameters 'e' and 'event' are incompatible.

React mengirim synthetic event miliknya sendiri, bukan event native. Pakai React.FocusEvent, atau import type { FocusEvent } from 'react' supaya namanya tidak tertimpa type global.

relatedTarget: ke mana fokus pindah

e.relatedTarget adalah elemen yang menerima fokus berikutnya, atau null kalau fokus keluar dari halaman atau tidak pindah ke elemen yang bisa difokus. Type-nya (EventTarget & Element) | null.

onBlur di React ikut bubble, beda dengan event blur native di browser, jadi kamu bisa memasangnya di container. Digabung dengan relatedTarget, kamu bisa tahu kapan fokus keluar dari sekelompok input:

function AddressFields() {
  const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
    if (!e.currentTarget.contains(e.relatedTarget)) {
      saveDraft()
    }
  }

  return (
    <div onBlur={handleBlur}>
      <input name="street" />
      <input name="city" />
    </div>
  )
}

Pindah di antara dua input tidak memanggil saveDraft. Tab keluar dari grup itu memanggilnya. contains(null) mengembalikan false, jadi klik di area kosong halaman juga dihitung keluar.

Di dalam handler ini, e.currentTarget adalah div-nya dan e.target adalah input yang kehilangan fokus.

Kesalahan yang sering terjadi

Tidak menulis generic. React.FocusEvent saja membuat target bertipe Element biasa, jadi .value error.

Memakai MouseEvent atau ChangeEvent. MouseEvent gagal compile. ChangeEvent lolos, seperti contoh di atas, tapi menyembunyikan relatedTarget.

Memakai FocusEvent native. Tanpa prefix React. kamu mendapat type DOM, yang tidak cocok dengan event yang dikirim React.

Kesimpulan

Beri type handler onBlur dengan React.FocusEvent<HTMLInputElement>, atau ganti type elemennya untuk textarea, select, dan container. Pakai React.FocusEventHandler<T> saat memberi type handler sebagai prop, dan baca relatedTarget kalau perlu tahu ke mana fokus pindah.