Reactjs Forms Controlled Uncontrolled Lifting State Validation Submit Flow
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama React.js untuk melanjutkan topik lainnya.
Kembali ke rujukan utama React.js untuk melanjutkan topik lainnya.
Form di React tetap memakai prinsip yang sama seperti di HTML biasa: ada input, ada nilai, ada event, lalu ada proses submit. Bedanya, React memberi kita kendali lebih jelas atas siapa yang menyimpan nilai input itu — komponen, DOM, atau kombinasi keduanya.
Di praktik nyata, tiga pola yang paling sering dipakai adalah:
ref saat dibutuhkan.ref atau library form.Topik ini penting karena form hampir selalu terlihat sederhana di awal, lalu cepat jadi rumit begitu ada validasi, field dinamis, dependensi antarfield, autosave, dan submit ke API.
Urutan di atas sengaja dipisah supaya kelihatan batasnya:
Controlled component adalah input yang nilai aktualnya dikendalikan oleh state React.
value diambil dari state.onChange mengubah state.import { useState } from 'react';
export default function ProfileForm() {
const [name, setName] = useState('');
return (
<form>
<label htmlFor="name">Nama</label>
<input
id="name"
Pakai controlled component kalau:
Uncontrolled component membiarkan DOM menyimpan nilai input. React hanya mengambil nilainya saat perlu, biasanya lewat ref.
import { useRef } from 'react';
export default function LoginForm() {
const emailRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
const
Pakai uncontrolled component kalau:
Simpan state form di komponen kalau nilai itu dibutuhkan untuk render atau logika UI.
Contoh kasus yang jelas:
Prinsip praktisnya begini:
Kalau perubahan nilai input harus langsung memengaruhi UI React, simpan di state.
Kalau form-nya kecil, state lokal di komponen form sering sudah cukup. Kalau bagian UI lain perlu tahu nilai form, state bisa dinaikkan ke parent.
Pakai ref kalau kamu hanya perlu membaca nilai atau mengakses elemen tanpa menjadikan nilai itu bagian dari alur render.
Kasus yang umum:
const nameRef = useRef(null);
function focusName() {
nameRef.current?.focus();
}Prinsip singkatnya:
Kalau nilai itu tidak perlu memicu render,
refsering lebih pas daripada state.
Lifting state berarti memindahkan state form ke komponen parent yang membawahi beberapa child. Ini dipakai saat beberapa komponen perlu membaca atau mengubah data yang sama.
function CheckoutPage() {
const [form, setForm] = useState({
name: '',
email: '',
});
return (
<>
<CustomerForm form={form} setForm=
Kalau state dinaikkan terlalu tinggi tanpa alasan, semua render ikut terbawa. Jadi lifting state itu alat koordinasi, bukan default untuk semua kasus.
Validasi form biasanya punya dua lapisan:
Cocok untuk:
Contoh pendek:
function validate(values) {
const errors = {};
if (!values.email) errors.email = 'Email wajib diisi';
if (values.password.length < 8) errors.password = 'Minimal 8 karakter';
return errors;
}Tetap wajib, karena client-side bisa dilewati. Server perlu memeriksa ulang:
Biasanya error disimpan terpisah dari nilai form:
values untuk data input,errors untuk pesan validasi,touched untuk menandai field yang sudah disentuh,isSubmitting untuk mencegah submit ganda.Ini membantu membedakan antara field kosong yang belum disentuh dan field yang memang salah.
Submit flow yang rapi biasanya seperti ini:
preventDefault() mencegah refresh halaman.ref.function handleSubmit(e) {
e.preventDefault();
const nextErrors = validate(values);
setErrors(nextErrors);
if (Object.keys(nextErrors).length > 0) return;
submitToServer(values);
}Kalau form tumbuh besar, state yang datar dan acak cepat sulit dirawat. Lebih baik grupkan sesuai domain:
values,errors,meta,submission.Ini sering bikin source of truth dobel. Kalau data datang dari parent, pertimbangkan apakah memang perlu disalin ke state lokal.
useEffectValidasi biasanya lebih aman dijalankan di event handler atau fungsi eksplisit. Kalau semua masuk effect, dependency jadi rumit dan bug makin samar.
Form besar dengan banyak input teks bisa terasa berat kalau setiap ketikan memicu kalkulasi mahal. Solusinya bukan langsung pindah semua ke uncontrolled, tapi pisahkan state, memoize bagian berat, atau gunakan library form yang efisien.
Kalau pesan error dicampur langsung ke value, alur baca jadi sulit. Pisahkan agar update satu tidak mengganggu yang lain.
Kalau tombol tidak dinonaktifkan saat request berjalan, user bisa mengirim form berkali-kali. Ini klasik, dan masih sering kejadian.
Saat user memilih opsi yang menyembunyikan field tertentu, data dan error field lama sering tertinggal. Pastikan state ikut disesuaikan saat struktur form berubah.
Pakai patokan ini:
Kalau ragu, pilih pola yang membuat data paling mudah dipahami oleh komponen yang benar-benar membutuhkannya.
useStateuseRefuseState, useEffect, useMemo, useCallback, dan useRef