Menguasai Form di React: Panduan Lengkap React Hook Form untuk Aplikasi Modern
1. Pendahuluan
Jika Anda seorang developer React, kemungkinan besar Anda sudah familiar dengan “kegembiraan” saat membangun dan mengelola form. Dari input teks sederhana hingga form pendaftaran yang kompleks dengan validasi berlapis dan input dinamis, form adalah tulang punggung interaksi pengguna di banyak aplikasi web. Namun, mengelola state form, validasi, dan memastikan performa yang baik di React seringkali bisa menjadi tantangan tersenduan.
Masalah umum yang sering muncul meliputi:
- Terlalu banyak boilerplate code: Untuk setiap input, Anda perlu state, handler
onChange, dan logika validasi. - Re-render yang tidak perlu: Setiap perubahan input seringkali memicu re-render seluruh komponen form, yang bisa berdampak pada performa, terutama pada form besar.
- Validasi yang kompleks: Mengimplementasikan validasi real-time, menampilkan pesan error, dan mengintegrasikannya dengan skema validasi eksternal (seperti Zod atau Yup) bisa memakan banyak waktu.
- Integrasi dengan UI library: Menggunakan komponen form dari Material UI, Ant Design, atau Chakra UI seringkali membutuhkan upaya ekstra agar bisa bekerja dengan baik.
Di sinilah React Hook Form (RHF) hadir sebagai penyelamat. RHF adalah library yang dirancang untuk mengatasi semua masalah ini, memungkinkan Anda membangun form yang lebih performa tinggi, lebih mudah dikelola, dan dengan developer experience yang jauh lebih baik. Mari kita selami lebih dalam!
2. Kenapa React Hook Form? Mengatasi Masalah Form Tradisional
Sebelum kita masuk ke praktik, mari kita pahami mengapa RHF begitu populer dan efektif. Pendekatan “tradisional” dalam mengelola form di React sering melibatkan controlled components, di mana setiap input terikat pada state React dan setiap perubahan memicu pembaruan state.
// Contoh form tradisional (controlled component)
import React, { useState } from 'react';
function FormTradisional() {
const [nama, setNama] = useState('');
const [email, setEmail] = useState('');
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!nama) newErrors.nama = 'Nama wajib diisi';
if (!email) newErrors.email = 'Email wajib diisi';
else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Format email tidak valid';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('Data form:', { nama, email });
alert('Form berhasil disubmit!');
} else {
console.log('Ada error dalam form.');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Nama:</label>
<input type="text" value={nama} onChange={(e) => setNama(e.target.value)} />
{errors.nama && <p style={{ color: 'red' }}>{errors.nama}</p>}
</div>
<div>
<label>Email:</label>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
{errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
📌 Masalah dengan pendekatan di atas:
- Re-render berlebihan: Setiap kali
setNamaatausetEmaildipanggil, komponenFormTradisionalakan re-render, yang bisa menjadi bottleneck performa pada form dengan banyak input. - Boilerplate: Untuk setiap input, Anda perlu
useStatedan fungsionChange. - Validasi manual: Logika validasi harus ditulis dan dikelola secara manual.
React Hook Form mengambil pendekatan yang berbeda, yaitu dengan memanfaatkan uncontrolled components secara default. Ini berarti RHF tidak mengontrol state setiap input secara langsung melalui React state, melainkan membaca nilai input langsung dari DOM saat dibutuhkan (misalnya, saat submit).
✅ Keunggulan React Hook Form:
- Performa tinggi: Minim re-render karena tidak setiap input terikat pada state React.
- Ukuran bundle kecil: Library yang ringan.
- Developer Experience (DX) yang luar biasa: API yang intuitif dan mudah digunakan.
- Mudah divalidasi: Integrasi mudah dengan validasi bawaan HTML dan skema validasi eksternal (Zod, Yup, Joi).
- Integrasi UI library yang mulus: Ada komponen
Controlleruntuk mengintegrasikan controlled components dari UI library.
3. Dasar-Dasar React Hook Form: useForm dan register
Mari kita mulai dengan form sederhana menggunakan React Hook Form.
Instalasi:
npm install react-hook-form
# atau
yarn add react-hook-form
Contoh Form Sederhana:
import React from 'react';
import { useForm } from 'react-hook-form';
function FormRHFDasar() {
// 1. Inisialisasi useForm hook
const { register, handleSubmit, formState: { errors } } = useForm();
// 2. Fungsi yang akan dipanggil saat form disubmit
const onSubmit = (data) => {
console.log('Data form:', data);
alert('Form berhasil disubmit!');
};
return (
<form onSubmit={handleSubmit(onSubmit)}> {/* 3. Hubungkan handleSubmit dengan onSubmit */}
<div>
<label>Nama:</label>
{/* 4. Daftarkan input menggunakan register() */}
<input type="text" {...register("nama", { required: "Nama wajib diisi" })} />
{/* 5. Tampilkan error jika ada */}
{errors.nama && <p style={{ color: 'red' }}>{errors.nama.message}</p>}
</div>
<div>
<label>Email:</label>
<input
type="email"
{...register("email", {
required: "Email wajib diisi",
pattern: {
value: /\S+@\S+\.\S+/,
message: "Format email tidak valid"
}
})}
/>
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default FormRHFDasar;
💡 Penjelasan:
-
useForm(): Ini adalah inti dari RHF. Ia mengembalikan objek dengan beberapa properti penting.register: Fungsi untuk mendaftarkan elemen input ke RHF. Ini mengaitkan input dengan nama field ("nama","email") dan menambahkan properti HTML yang diperlukan (sepertiname,onBlur,onChange,ref).handleSubmit: Fungsi wrapper yang Anda berikan fungsionSubmitAnda. Ia akan menanganie.preventDefault()dan hanya memanggilonSubmitAnda jika validasi berhasil.formState: { errors }: Objek yang berisi semua error validasi saat ini.
-
{...register("nama", { ... })}: Ini adalah cara kita “mendaftarkan” input. Argumen pertama adalah nama field (yang akan menjadi kunci di objek data saat submit). Argumen kedua adalah objek konfigurasi validasi. -
errors.nama.message: RHF akan secara otomatis mengisi objekerrorsjika ada masalah validasi. Propertimessageberisi pesan error yang Anda definisikan.
4. Validasi Form yang Kuat: Dari Bawaan hingga Schema Validation
Salah satu kekuatan utama RHF adalah sistem validasinya yang fleksibel.
Validasi Bawaan HTML5
RHF secara otomatis mendukung atribut validasi HTML5 seperti required, min, max, minLength, maxLength, dan pattern. Anda cukup melewatkannya sebagai objek konfigurasi ke fungsi register.
<input
type="text"
{...register("username", {
required: "Username wajib diisi",
minLength: {
value: 5,
message: "Username minimal 5 karakter"
},
maxLength: {
value: 20,
message: "Username maksimal 20 karakter"
}
})}
/>
{errors.username && <p style={{ color: 'red' }}>{errors.username.message}</p>}
Validasi Kustom
Anda juga bisa menambahkan fungsi validasi kustom menggunakan properti validate.
<input
type="password"
{...register("password", {
required: "Password wajib diisi",
minLength: {
value: 8,
message: "Password minimal 8 karakter"
},
validate: (value) =>
value.includes('!') || "Password harus mengandung setidaknya satu tanda seru (!)"
})}
/>
{errors.password && <p style={{ color: 'red' }}>{errors.password.message}</p>}
Integrasi dengan Schema Validation (Zod, Yup, dll.)
Untuk form yang lebih kompleks, mengelola validasi langsung di register bisa menjadi rumit. RHF sangat mendukung integrasi dengan library schema validation populer seperti Zod, Yup, atau Joi. Ini memungkinkan Anda mendefinisikan skema validasi terpusat dan mendapatkan manfaat dari inferensi tipe (terutama dengan TypeScript).
Untuk menggunakan Zod, Anda perlu menginstal @hookform/resolvers:
npm install @hookform/resolvers zod
Contoh dengan Zod:
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; // Import resolver
import { z } from 'zod'; // Import Zod
// 1. Definisikan skema validasi menggunakan Zod
const userSchema = z.object({
nama: z.string().min(1, { message: "Nama wajib diisi" }),
email: z.string().email({ message: "Format email tidak valid" }),
umur: z.number().min(18, { message: "Anda harus berusia minimal 18 tahun" }).optional(),
});
function FormRHFZod() {
const { register, handleSubmit, formState: { errors } } = useForm({
// 2. Gunakan zodResolver untuk mengintegrasikan skema
resolver: zodResolver(userSchema),
defaultValues: { // Opsional: nilai default
nama: "",
email: "",
umur: undefined,
}
});
const onSubmit = (data) => {
console.log('Data form (dengan Zod):', data);
alert('Form berhasil disubmit!');
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Nama:</label>
<input type="text" {...register("nama")} />
{errors.nama && <p style={{ color: 'red' }}>{errors.nama.message}</p>}
</div>
<div>
<label>Email:</label>
<input type="email" {...register("email")} />
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
</div>
<div>
<label>Umur (Opsional):</label>
<input type="number" {...register("umur", { valueAsNumber: true })} /> {/* Penting: valueAsNumber untuk input number */}
{errors.umur && <p style={{ color: 'red' }}>{errors.umur.message}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default FormRHFZod;
💡 Dengan Zod, Anda tidak perlu lagi menulis validasi di setiap register. Cukup definisikan skema sekali, dan RHF akan menanganinya. Ini sangat meningkatkan keterbacaan, maintainability, dan keamanan tipe kode Anda.
5. Mengelola Tipe Input Berbeda dan Controller untuk UI Library
RHF sangat fleksibel untuk berbagai tipe input.
Tipe Input HTML Standar
Untuk text, email, password, checkbox, radio, select, Anda bisa menggunakan register seperti biasa.
-
Checkbox:
<input type="checkbox" {...register("setuju")} /> Saya setuju {errors.setuju && <p style={{ color: 'red' }}>{errors.setuju.message}</p>} -
Select:
<select {...register("negara")}> <option value="">Pilih Negara</option> <option value="ID">Indonesia</option> <option value="MY">Malaysia</option> </select> {errors.negara && <p style={{ color: 'red' }}>{errors.negara.message}</p>}
Controller untuk Komponen UI Library
Banyak UI library (seperti Material UI, Ant Design, Chakra UI) menggunakan controlled components untuk input mereka, yang berarti mereka memerlukan value dan onChange secara eksplisit. register RHF bekerja paling baik dengan uncontrolled components. Di sinilah Controller dari RHF masuk.
Controller adalah komponen yang bertindak sebagai wrapper untuk komponen input eksternal, menjembatani RHF dengan properti value dan onChange yang dibutuhkan.
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { TextField, Button } from '@mui/material'; // Contoh dari Material UI
function FormRHFMaterialUI() {
const { handleSubmit, control, formState: { errors } } = useForm({
defaultValues: {
namaLengkap: '',
},
});
const onSubmit = (data) => console.log('Data form (Material UI):', data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="namaLengkap"
control={control}
rules={{ required: "Nama lengkap wajib diisi" }} // Validasi bisa di sini juga
render={({ field }) => (
<TextField
{...field} // Memasukkan value, onChange, onBlur, dll.
label="Nama Lengkap"
variant="outlined"
fullWidth
error={!!errors.namaLengkap}
helperText={errors.namaLengkap ? errors.namaLengkap.message : ''}
/>
)}
/>
<Button type="submit" variant="contained" style={{ marginTop: '1rem' }}>
Submit
</Button>
</form>
);
}
export default FormRHFMaterialUI;
💡 Dengan Controller, Anda bisa menggunakan komponen input dari UI library favorit Anda tanpa mengorbankan performa atau kemudahan validasi RHF.
6. Performa dan Fitur Lanjutan
Optimasi Re-render
Seperti yang disebutkan, RHF dirancang untuk meminimalkan re-render. Secara default, hanya komponen yang menampilkan error yang akan re-render saat validasi gagal. Perubahan input tidak akan menyebabkan seluruh form re-render.
Anda bisa mengontrol kapan validasi dipicu dengan opsi mode di useForm:
mode: "onSubmit"(default): Validasi dipicu saat submit.mode: "onBlur": Validasi dipicu saat input kehilangan fokus.mode: "onChange": Validasi dipicu pada setiap perubahan input (hati-hati, ini bisa menyebabkan banyak re-render).mode: "onTouched": Validasi dipicu saat input disentuh pertama kali.
Mengelola Nilai Form (setValue, watch, getValues)
Selain register, RHF menyediakan beberapa fungsi untuk berinteraksi dengan nilai form:
setValue('fieldName', value): Mengatur nilai field secara programatis. Berguna untuk mengisi form dari data API atau mengatur nilai default.watch('fieldName'): Mengamati perubahan nilai field secara real-time. Berguna untuk menampilkan nilai atau mengaktifkan/menonaktifkan input lain berdasarkan input tertentu.getValues('fieldName')ataugetValues(): Mendapatkan nilai field saat ini secara langsung, tanpa memicu re-render. Berguna saat Anda hanya perlu membaca nilai tanpa mengamati perubahannya.
import React from 'react';
import { useForm } from 'react-hook-form';
function FormRHFInteraksi() {
const { register, handleSubmit, watch, setValue, getValues } = useForm();
const namaYangDitonton = watch("nama"); // Mengamati perubahan nama
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Nama:</label>
<input type="text" {...register("nama")} />
<p>Nama saat ini (watch): {namaYangDitonton}</p> {/* Akan re-render saat nama berubah */}
</div>
<button type="button" onClick={() => setValue("nama", "Budi Santoso")}>
Set Nama ke Budi
</button>
<button type="button" onClick={() => alert(getValues("nama"))}>
Get Nama (tanpa re-render)
</button>
<button type="submit">Submit</button>
</form>
);
}
Form Dinamis (Field Array)
Untuk kasus seperti daftar item yang bisa ditambahkan/dihapus (misalnya, daftar pengalaman kerja), RHF menyediakan useFieldArray hook. Ini adalah topik yang lebih dalam, tapi sangat kuat untuk mengelola form array.
Kesimpulan
React Hook Form adalah alat yang sangat powerful dan esensial untuk setiap developer React yang serius dalam membangun aplikasi web modern. Dengan fokus pada performa, developer experience, dan fleksibilitas validasi, RHF memungkinkan Anda mengatasi kompleksitas form dengan elegan.
Dari form pendaftaran sederhana hingga form multi-langkah yang rumit dengan validasi skema eksternal dan integrasi UI library, RHF menyediakan semua yang Anda butuhkan untuk membangun form yang robust, performa tinggi, dan mudah dikelola. Mulailah menggunakannya di proyek Anda berikutnya, dan rasakan perbedaannya!
🔗 Baca Juga
- Atom-based State Management dengan Recoil/Jotai: Membangun Aplikasi React yang Cerdas dan Skalabel
- Zustand: State Management Simpel dan Kuat untuk Aplikasi React Modern
- Membangun Custom Hooks yang Kuat dan Reusable: Mengoptimalkan Logika dan State di Aplikasi React Anda
- Optimasi Data Fetching di Frontend: Menggali Lebih Dalam React Query (TanStack Query) dan SWR