REACT FORM-MANAGEMENT FRONTEND JAVASCRIPT WEB-DEVELOPMENT DEVELOPER-EXPERIENCE VALIDATION STATE-MANAGEMENT PERFORMANCE UI-UX

Menguasai Form di React: Panduan Lengkap React Hook Form untuk Aplikasi Modern

⏱️ 14 menit baca
👨‍💻

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:

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:

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:

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:

  1. 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 (seperti name, onBlur, onChange, ref).
    • handleSubmit: Fungsi wrapper yang Anda berikan fungsi onSubmit Anda. Ia akan menangani e.preventDefault() dan hanya memanggil onSubmit Anda jika validasi berhasil.
    • formState: { errors }: Objek yang berisi semua error validasi saat ini.
  2. {...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.

  3. errors.nama.message: RHF akan secara otomatis mengisi objek errors jika ada masalah validasi. Properti message berisi 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.

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:

Mengelola Nilai Form (setValue, watch, getValues)

Selain register, RHF menyediakan beberapa fungsi untuk berinteraksi dengan nilai form:

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