# Form Type-Safe dengan React Hook Form dan Zod

> Cara menghubungkan React Hook Form dengan resolver Zod, memakai satu schema yang sama di client dan server, serta mendapatkan penanganan error yang type-safe tanpa usaha ekstra.

URL: https://www.matthewswong.com/id/blog/react-hook-form-zod-type-safe-forms/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-07-09
- Updated: 2026-07-09
- Category: Web Dev

Setiap form pada akhirnya menghadapi tiga masalah yang sama: validasi di client mulai berbeda dari validasi di server, tipe TypeScript ikut menyimpang dari keduanya, dan pesan error tersebar di berbagai komponen alih-alih berada di dekat aturan yang mendefinisikannya. React Hook Form yang dipasangkan dengan Zod menyelesaikan ketiga masalah ini sekaligus, dan caranya lebih sederhana dari yang biasanya dibayangkan.

Tulisan ini membahas setup ala production: satu schema Zod yang mendefinisikan aturan validasi sekaligus menjadi tipe TypeScript, zodResolver yang dipasang ke useForm, route API yang memvalidasi ulang schema yang sama di sisi server, dan pola callout yang perlu diketahui sebelum form dirilis ke pengguna nyata.

## Definisikan schema sekali, turunkan tipenya dari sana

Ide utamanya adalah berhenti menulis interface TypeScript dan fungsi validasi secara terpisah. Schema Zod mendeskripsikan bentuk data sekaligus aturan yang harus dipenuhi, dan TypeScript bisa membaca tipe tersebut langsung dari schema menggunakan utilitas inferensi bawaannya. Ubah satu aturan di schema, dan tipe tersebut ikut ter-update secara otomatis, sehingga komponen form, handler submit, dan route API tetap sinkron tanpa perlu diedit ulang.

> Jaga agar file schema tetap bebas dari framework tertentu. Schema di schemas slash contact form dot ts sebaiknya tidak mengimpor apa pun dari React Hook Form atau Next.js, sehingga file yang sama persis bisa dipakai baik di form pada browser maupun di route API tanpa kode adapter tambahan.

## Pasangkan resolver ke useForm

Paket hookform resolvers menyediakan fungsi zodResolver yang mengubah schema Zod menjadi bentuk resolver yang dibutuhkan React Hook Form. Dengan memasukkan schema melalui zodResolver ke useForm, setiap field yang didaftarkan lewat fungsi register akan divalidasi dengan aturan yang sama, dan objek errors pada formState terisi dengan pesan yang persis didefinisikan di schema, tanpa logika validasi terpisah di komponen.

Perhatikan bahwa komponen tidak pernah memanggil parse atau safeParse secara manual. React Hook Form menjalankan resolver secara internal pada setiap pemicu validasi, baik itu onBlur, onChange, maupun saat submit, tergantung mode yang dikonfigurasi. Komponen hanya membaca objek errors yang dihasilkan dan flag isSubmitting, sehingga JSX tetap fokus pada tampilan alih-alih urusan validasi.

## Validasi ulang schema yang sama di sisi server

Validasi di sisi client adalah fitur pengalaman pengguna, bukan batas keamanan. Sebuah request selalu bisa mencapai route API tanpa melewati form, entah dari ekstensi browser, request yang diputar ulang, atau client yang sama sekali berbeda, sehingga aturan yang sama harus dijalankan lagi di server. Mengimpor schema yang identik ke dalam route handler dan memanggil safeParse memberikan tiga keuntungan sekaligus:

- Server menolak payload yang tidak valid dengan pesan per-field yang persis sama dengan yang sudah ditampilkan di client, sehingga kode penanganan error tidak digandakan dalam bentuk yang berbeda.
- Karena schema berada di modul yang dipakai bersama, perubahan aturan dijamin berlaku di kedua sisi begitu kode di-deploy, menutup celah klasik di mana validasi client dan server perlahan menyimpang seiring waktu.
- result dot data dari pemanggilan safeParse yang berhasil sudah bertipe sesuai tipe hasil inferensi schema, sehingga sisa kode handler bekerja dengan data yang sudah bertipe penuh dan sudah tervalidasi, bukan body request yang belum diketahui tipenya.

> Jangan melewatkan validasi di sisi server hanya karena client sudah memeriksa field-nya. Bundle di client bisa diperiksa dan diubah langsung dari browser, dan setiap endpoint yang bisa diakses lewat jaringan harus berasumsi bahwa body request belum tervalidasi.

## Memilih mode pemicu validasi

useForm menerima opsi mode yang menentukan kapan resolver benar-benar dijalankan, dan memilih mode yang salah adalah sumber umum form yang terasa lambat atau justru terlalu cerewet. Tabel berikut mencakup tiga mode yang paling sering dipakai pada form production.

Mode

Kapan divalidasi

Trade-off

onSubmit

Hanya saat form disubmit

Terasa tenang saat mengetik, tapi pengguna baru menemukan kesalahan setelah menekan submit.

onBlur

Saat field kehilangan fokus, lalu live setelah error pertama muncul

Default terbaik untuk kebanyakan form — feedback muncul tepat setelah pengguna selesai mengisi satu field.

onChange

Setiap kali ada ketikan

Feedback langsung, tapi bisa terasa mengganggu pada field yang panjang seperti password atau teks bebas.

## Aturan antar-field dengan refine

Sebagian aturan validasi tidak bisa diungkapkan pada satu field saja, contoh klasiknya adalah field konfirmasi password yang harus cocok dengan password aslinya. Metode refine milik Zod berjalan setelah aturan per-field lolos, dan memungkinkan perbandingan beberapa field sekaligus, lalu melekatkan error yang dihasilkan ke path field tertentu sehingga React Hook Form menampilkannya di tempat yang tepat.

Opsi path pada pemanggilan refine adalah yang membuat ini berguna dalam sebuah form. Tanpa opsi ini, error akan muncul sebagai error level-form tanpa field yang jelas untuk dilekatkan. Dengan path diarahkan ke field konfirmasi password, objek errors milik React Hook Form menampilkannya persis seperti error field lainnya, sehingga JSX tidak perlu penanganan khusus untuk merender error tersebut.

> Karena refine berjalan sebagai bagian dari schema yang sama, aturan antar-field ini diberlakukan secara identik di client maupun server, tanpa kode tambahan di kedua sisi.

## Checklist singkat sebelum merilis form

Ada beberapa kebiasaan yang membedakan form yang terlihat type-safe dari form yang benar-benar type-safe. Periksa poin-poin ini sebelum melakukan merge:

- Pastikan komponen tidak pernah mendeklarasikan ulang tipe yang seharusnya bisa diturunkan dari schema — tipe yang digandakan adalah hal pertama yang menyimpang.
- Pastikan route API mengimpor file schema yang sama dengan komponen client, bukan salinan atau duplikat yang ditulis ulang secara manual.
- Pastikan setiap field array dan field opsional memiliki nilai default yang jelas di defaultValues milik useForm, karena field opsional pada Zod bisa membuat input menjadi uncontrolled jika tidak diatur.
- Pastikan pesan error ditulis untuk pengguna akhir, bukan untuk developer — pesan seharusnya menjelaskan apa yang harus diperbaiki, bukan sekadar menyatakan bahwa suatu aturan gagal.

> Pecah form yang besar menjadi beberapa schema Zod yang lebih kecil, lalu gabungkan dengan merge atau extend alih-alih menulis satu schema objek yang sangat besar. Cara ini membuat setiap bagian tetap mudah diuji sendiri dan mempermudah pembuatan wizard multi-langkah di kemudian hari.

## Menguji schema, bukan menguji form

Karena aturan validasi berada di file schema biasa tanpa dependensi ke React atau DOM, aturan tersebut bisa diuji langsung sebagai unit test tanpa perlu merender satu komponen pun. File test yang mengimpor schema dan memanggil safeParse dengan beberapa payload valid dan tidak valid akan menangkap regresi aturan jauh lebih cepat dibanding test berbasis browser yang harus merender form, mengisi field, lalu memeriksa teks error yang ditampilkan.

Ini juga berarti test schema sekaligus berfungsi sebagai dokumentasi hidup untuk setiap aturan dalam form. Reviewer yang ingin tahu persis apa yang membuat field email tidak valid tidak perlu menelusuri kode komponen — jawabannya sudah ada di file test schema, dinyatakan sebagai contoh payload konkret yang lolos atau gagal.

Pola pada tulisan ini bisa diterapkan mulai dari form kontak dua field hingga alur checkout multi-langkah tanpa mengubah bentuknya: satu schema, satu tipe hasil inferensi, satu resolver, dan satu pemanggilan safeParse di server yang mencerminkan client. Begitu schema bersama ini ada, menambah satu field berarti mengedit tepat satu file, bukan tiga.

## Sumber

- Zod — validasi schema TypeScript-first
- Zod — definisi schema dan API refine atau superRefine
- react-hook-form atau resolvers — zodResolver di GitHub

> **Key takeaway:** React Hook Form yang dipasangkan dengan schema Zod memungkinkan satu file mendefinisikan aturan validasi, menurunkan tipe TypeScript secara otomatis, dan memvalidasi ulang aturan yang sama persis di route API lewat safeParse, sehingga validasi client, validasi server, dan tipe data tidak pernah saling menyimpang. Aturan antar-field memakai metode refine milik Zod, dan schema itu sendiri bisa diuji unit tanpa merender form sama sekali.

## Source links

- https://zod.dev/
- https://zod.dev/api
- https://github.com/react-hook-form/resolvers

## FAQ

### Apa fungsi zodResolver di React Hook Form?

zodResolver mengubah schema Zod agar bisa dipakai React Hook Form sebagai resolver validasi. Fungsi ini diimpor dari paket hookform resolvers dan dimasukkan ke opsi resolver pada useForm, sehingga setiap field yang didaftarkan divalidasi sesuai aturan schema dan pesan hasilnya otomatis mengisi errors pada formState.

### Mengapa sebaiknya schema Zod yang sama dipakai bersama di client dan server?

Memakai satu schema berarti aturan validasi dan tipe TypeScript hanya ada di satu tempat, sehingga perubahan aturan otomatis berlaku di mana pun schema tersebut diimpor. Ini juga menutup celah umum di mana pengecekan di client dan di server perlahan berbeda setelah beberapa kali diedit, karena kedua sisi menjalankan kode yang benar-benar sama.

### Bisakah tipe TypeScript diambil langsung dari schema Zod tanpa menulisnya secara terpisah?

Bisa. Utilitas inferensi milik Zod membaca bentuk dan aturan sebuah schema lalu menghasilkan tipe TypeScript yang sesuai secara otomatis. Artinya props form, generic pada useForm, dan tipe payload route API bisa merujuk ke tipe hasil inferensi yang sama, bukan tiga interface yang dikelola secara terpisah.

### Bagaimana cara memvalidasi satu field terhadap field lain, misalnya input konfirmasi password?

Gunakan metode refine milik Zod setelah schema objek didefinisikan. Metode ini berjalan setelah setiap field lolos aturannya masing-masing, memungkinkan perbandingan dua field atau lebih, dan menerima opsi path yang memberi tahu React Hook Form field mana yang harus menampilkan pesan error tersebut.

### Apakah validasi di sisi client dengan React Hook Form dan Zod sudah cukup tanpa validasi server?

Tidak. Validasi di sisi client hanya meningkatkan pengalaman pengguna; ia tidak melindungi route API dari request yang melewati form sama sekali. Schema Zod yang sama harus diimpor ke route server dan dijalankan lewat safeParse agar data yang tidak valid tetap ditolak dari mana pun request itu berasal.

---

- HTML version: https://www.matthewswong.com/id/blog/react-hook-form-zod-type-safe-forms/
- English: https://www.matthewswong.com/en/blog/react-hook-form-zod-type-safe-forms/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
