Reactjs Forms Controlled Uncontrolled Lifting State Validation Submit Flow
Definisi
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:
- Controlled component: nilai input disimpan di state React.
- Uncontrolled component: nilai input dibiarkan disimpan di DOM, lalu dibaca lewat
refsaat dibutuhkan. - Hybrid form: sebagian field dikontrol React, sebagian cukup dibaca lewat
refatau 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.
Gambaran alur form
Urutan di atas sengaja dipisah supaya kelihatan batasnya:
- input terjadi di level event browser,
- state dipakai kalau React perlu tahu nilai terbaru,
- validation berjalan sebelum atau sesudah submit,
- submit adalah titik integrasi ke server atau side effect lain.
Controlled component
Controlled component adalah input yang nilai aktualnya dikendalikan oleh state React.
Ciri utama
valuediambil dari state.onChangemengubah state.- React menjadi source of truth untuk nilai field.
import { useState } from 'react';
export default function ProfileForm() {
const [name, setName] = useState('');
return (
<form>
<label htmlFor="name">Nama</label>
<input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</form>
);
}Kapan dipakai
Pakai controlled component kalau:
- nilai input perlu langsung dipakai untuk render,
- ada validasi real-time,
- ada dependensi antarfield,
- nilai form perlu disinkronkan ke parent,
- form punya pola interaktif seperti autocomplete, masking, atau conditional fields.
Kelebihan
- mudah divalidasi saat mengetik,
- mudah direset,
- mudah diserialisasi,
- state form terlihat jelas di React DevTools,
- cocok untuk form kompleks yang butuh orkestrasi.
Kekurangan
- lebih banyak rerender,
- code boilerplate lebih panjang,
- kalau semua field besar dikontrol tanpa disiplin, komponen cepat gemuk.
Uncontrolled component
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 email = emailRef.current?.value;
console.log(email);
}
return (
<form onSubmit={handleSubmit}>
<input ref={emailRef} type="email" />
<button type="submit">Kirim</button>
</form>
);
}Kapan dipakai
Pakai uncontrolled component kalau:
- form sederhana,
- nilai field hanya dibutuhkan saat submit,
- kamu perlu integrasi dengan input native atau library non-React,
- performa rerender jadi perhatian, terutama pada form besar,
- input memang lebih cocok dibaca sekali lewat DOM.
Kelebihan
- implementasi ringan,
- tidak rerender setiap ketikan,
- cocok untuk input yang benar-benar “isi lalu submit”.
Kekurangan
- state form tidak gampang dibaca untuk UI yang dinamis,
- validasi real-time lebih sulit,
- reset dan sinkronisasi antarfield jadi kurang nyaman,
- source of truth terbagi antara React dan DOM.
Kapan state form disimpan di komponen
Simpan state form di komponen kalau nilai itu dibutuhkan untuk render atau logika UI.
Contoh kasus yang jelas:
- menampilkan error di bawah field,
- mengaktifkan atau menonaktifkan tombol submit,
- menampilkan preview dari input,
- menampilkan field tambahan berdasarkan pilihan user,
- mengubah label, helper text, atau status berdasarkan isi field.
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.
Kapan pakai ref
Pakai ref kalau kamu hanya perlu membaca nilai atau mengakses elemen tanpa menjadikan nilai itu bagian dari alur render.
Kasus yang umum:
- membaca nilai uncontrolled input saat submit,
- memindahkan fokus ke field yang error,
- scroll ke field tertentu,
- reset native input tertentu,
- integrasi dengan third-party widget yang mengelola DOM sendiri.
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
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.
Kenapa dilakukan
- field berada di komponen berbeda,
- summary panel perlu membaca isi form,
- wizard step berikutnya butuh data step sebelumnya,
- tombol submit global perlu tahu form valid atau belum.
Contoh pola
function CheckoutPage() {
const [form, setForm] = useState({
name: '',
email: '',
});
return (
<>
<CustomerForm form={form} setForm={setForm} />
<OrderSummary form={form} />
</>
);
}Kalau state dinaikkan terlalu tinggi tanpa alasan, semua render ikut terbawa. Jadi lifting state itu alat koordinasi, bukan default untuk semua kasus.
Validation
Validasi form biasanya punya dua lapisan:
- Client-side validation untuk feedback cepat.
- Server-side validation untuk otoritas akhir.
Client-side validation
Cocok untuk:
- required field,
- format email,
- panjang minimal/maksimal,
- kecocokan password dan confirm password,
- aturan yang bisa dicek sebelum request.
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;
}Server-side validation
Tetap wajib, karena client-side bisa dilewati. Server perlu memeriksa ulang:
- data wajib,
- format,
- izin akses,
- duplikasi data,
- rule bisnis yang sensitif.
Pola error state
Biasanya error disimpan terpisah dari nilai form:
valuesuntuk data input,errorsuntuk pesan validasi,toucheduntuk menandai field yang sudah disentuh,isSubmittinguntuk mencegah submit ganda.
Ini membantu membedakan antara field kosong yang belum disentuh dan field yang memang salah.
Submit flow
Submit flow yang rapi biasanya seperti ini:
- User menekan submit.
preventDefault()mencegah refresh halaman.- Form values dikumpulkan dari state atau
ref. - Validasi client-side dijalankan.
- Kalau valid, request dikirim ke server.
- Server mengembalikan sukses atau error.
- UI menampilkan hasil akhir, reset, atau error.
function handleSubmit(e) {
e.preventDefault();
const nextErrors = validate(values);
setErrors(nextErrors);
if (Object.keys(nextErrors).length > 0) return;
submitToServer(values);
}Hal yang perlu dijaga
- disable tombol submit saat request masih berjalan,
- jangan kirim request kalau validasi client-side gagal,
- tampilkan error server di tempat yang masuk akal,
- kalau gagal, pertahankan input user agar tidak hilang.
Jebakan umum pada form kompleks
1. Semua field disimpan tanpa struktur
Kalau form tumbuh besar, state yang datar dan acak cepat sulit dirawat. Lebih baik grupkan sesuai domain:
values,errors,meta,submission.
2. Menyalin props ke state tanpa kebutuhan jelas
Ini sering bikin source of truth dobel. Kalau data datang dari parent, pertimbangkan apakah memang perlu disalin ke state lokal.
3. Validasi ditaruh terlalu banyak di useEffect
Validasi biasanya lebih aman dijalankan di event handler atau fungsi eksplisit. Kalau semua masuk effect, dependency jadi rumit dan bug makin samar.
4. Controlled input jadi berat
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.
5. Error state dan value state tercampur
Kalau pesan error dicampur langsung ke value, alur baca jadi sulit. Pisahkan agar update satu tidak mengganggu yang lain.
6. Submit ganda
Kalau tombol tidak dinonaktifkan saat request berjalan, user bisa mengirim form berkali-kali. Ini klasik, dan masih sering kejadian.
7. Field dinamis tidak dibersihkan
Saat user memilih opsi yang menyembunyikan field tertentu, data dan error field lama sering tertinggal. Pastikan state ikut disesuaikan saat struktur form berubah.
Pola keputusan cepat
Pakai patokan ini:
- Controlled kalau UI harus reaktif terhadap nilai input.
- Uncontrolled + ref kalau nilai cukup dibaca saat submit.
- Lift state kalau beberapa komponen perlu data yang sama.
- State lokal kalau form kecil dan mandiri.
- Validation di client untuk feedback cepat, validation di server untuk final check.
Kalau ragu, pilih pola yang membuat data paling mudah dipahami oleh komponen yang benar-benar membutuhkannya.
Referensi terkait
- React Forms
useStateuseRef- Component, Props, State, dan Render Flow
- Hooks Dasar React.js:
useState,useEffect,useMemo,useCallback, danuseRef - HTML Forms, Inputs, Labels, Validation, dan Accessibility