Menyederhanakan Penanganan Form di Next.js dengan React Hook Form dan TypeScript
Membangun form registrasi yang type-safe di Next.js dengan React Hook Form dan skema Zod — mulai dari setup, validasi, sampai memasang komponennya ke sebuah halaman.

Form adalah bagian tak terpisahkan dari aplikasi web modern, dan mengelola state serta validasinya secara efisien sering kali jadi tantangan tersendiri. Di sinilah React Hook Form unggul. Pada artikel ini kita akan membahas cara mengintegrasikan React Hook Form ke dalam proyek Next.js menggunakan TypeScript demi type safety dan pengalaman pengembangan yang lebih baik.
Kenapa React Hook Form?
React Hook Form (RHF) menyederhanakan penanganan form di aplikasi React dengan menyediakan:
- Re-render seminimal mungkin, sehingga performanya tinggi.
- API yang sederhana lewat hook seperti useForm.
- Dukungan validasi bawaan dengan library seperti Yup atau Zod.
- Dukungan TypeScript yang sangat baik untuk definisi tipe yang solid.
Dipadukan dengan Next.js, RHF membuat pembuatan form terasa mulus, bahkan pada aplikasi kompleks yang di-render di server.
Menyiapkan Proyek
Pertama, buat proyek Next.js baru dengan dukungan TypeScript:
npx create-next-app@latest my-nextjs-app --typescript
cd my-nextjs-appSelanjutnya, pasang dependensi yang dibutuhkan:
npm install react-hook-form @hookform/resolvers zod- react-hook-form: library inti untuk penanganan form.
- @hookform/resolvers: menyediakan dukungan validasi berbasis skema untuk library seperti Zod.
- zod: library validasi skema yang mengutamakan TypeScript.
Membuat Form dengan React Hook Form
Mari kita buat form registrasi sederhana dengan field berikut:
- Name (wajib, string)
- Email (wajib, format email)
- Password (wajib, minimal 6 karakter)
- Repeat Password (wajib, harus sama dengan Password)
Langkah 1: Definisikan Skema
Dengan Zod, definisikan skema validasinya:
import { z } from 'zod';
export const registrationSchema = z.object({
name: z.string().nonempty('Name is required'),
email: z.string().email('Invalid email address'),
password: z.string().min(6, 'Password must be at least 6 characters long'),
repeatPassword: z.string().min(6, 'Repeat Password must be at least 6 characters long'),
}).refine((data) => data.password === data.repeatPassword, {
message: 'Passwords do not match',
path: ['repeatPassword'],
});
export type RegistrationFormValues = z.infer<typeof registrationSchema>;Langkah 2: Bangun Komponennya
Buat komponen RegistrationForm:
import { useForm, SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { registrationSchema, RegistrationFormValues } from "./schemas/registrationSchema";
const RegistrationForm = () => {
const form = useForm<RegistrationFormValues>({
resolver: zodResolver(registrationSchema),
});
const errors = form.formState.errors;
const onSubmit: SubmitHandler<RegistrationFormValues> = (data) => {
console.log(data);
};
return (
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Name
</label>
<input
id="name"
type="text"
{...form.register("name")}
className={`mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm ${
errors.name ? "border-red-500" : ""
}`}
/>
{errors.name && <p className="mt-1 text-sm text-red-500">{errors.name.message}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email
</label>
<input
id="email"
type="email"
{...form.register("email")}
className={`mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm ${
errors.email ? "border-red-500" : ""
}`}
/>
{errors.email && <p className="mt-1 text-sm text-red-500">{errors.email.message}</p>}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<input
id="password"
type="password"
{...form.register("password")}
className={`mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm ${
errors.password ? "border-red-500" : ""
}`}
/>
{errors.password && <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>}
</div>
<div>
<label htmlFor="repeatPassword" className="block text-sm font-medium text-gray-700">
Repeat Password
</label>
<input
id="repeatPassword"
type="password"
{...form.register("repeatPassword")}
className={`mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm ${
errors.repeatPassword ? "border-red-500" : ""
}`}
/>
{errors.repeatPassword && (
<p className="mt-1 text-sm text-red-500">{errors.repeatPassword.message}</p>
)}
</div>
<button
type="submit"
className="w-full rounded-md bg-indigo-600 py-2 px-4 text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Register
</button>
</form>
);
};
export default RegistrationForm;Langkah 3: Integrasikan ke Sebuah Halaman
Buat halaman baru di app/register/page.tsx:
import RegistrationForm from '../../components/RegistrationForm';
const RegisterPage = () => {
return (
<div className="mx-auto max-w-md py-10">
<h1 className="text-2xl font-bold text-center mb-6">Register</h1>
<RegistrationForm />
</div>
);
};
export default RegisterPage;Menguji Form
Jalankan development server:
npm run devBuka http://localhost:3000/register untuk melihat formnya bekerja. Coba kirim form dengan input yang tidak valid untuk melihat pesan validasinya.
Peningkatan dan Praktik Terbaik
- Styling: gunakan library seperti Tailwind CSS atau CSS Modules untuk menata tampilan form Anda.
- Validasi Dinamis: sesuaikan validasi secara dinamis mengikuti interaksi pengguna.
- Integrasi API: kirim data form ke server menggunakan fetch atau axios.
- Penanganan Error: tangani error dari sisi server dengan baik dengan menampilkan umpan balik ke pengguna.
Kesimpulan
Memadukan React Hook Form dengan Next.js dan TypeScript menghasilkan kombinasi yang ampuh untuk membangun form yang tangguh dan nyaman dipakai. Dengan boilerplate yang minim, type safety yang kuat, dan performa yang baik, RHF membuat developer bisa fokus menciptakan pengalaman pengguna yang baik.
Coba di proyek Anda berikutnya dan rasakan bedanya!


