Artikel ini membahas tentang perjuangan umum yang dihadapi pengembang ketika berurusan dengan formulir — dan bagaimana React 19 akhirnya memperkenalkan beberapa alat yang telah lama ditunggu yang membuat penanganan formulir lebih bersih, lebih deklaratif, dan jauh lebih minim kesalahan.
Selama enam tahun terakhir dalam pengembangan frontend — dari membangun sistem formulir kompleks hingga mengintegrasikan alat AI di SDG — saya telah menulis, men-debug, dan merefaktor lebih banyak kode formulir daripada yang ingin saya akui.
Dan jika Anda pernah membangun atau memelihara formulir di React, Anda mungkin merasakan hal yang sama. Formulir tampak sederhana... sampai tidak lagi.
Dalam artikel ini, saya akan membahas perjuangan umum yang dihadapi pengembang ketika berurusan dengan formulir — dan bagaimana React 19 akhirnya memperkenalkan beberapa alat yang telah lama ditunggu yang membuat penanganan formulir lebih bersih, lebih deklaratif, dan jauh lebih minim kesalahan. ✨
🔍 Mari mulai dengan titik-titik masalah yang pernah dihadapi setiap pengembang React setidaknya sekali.
Mengelola state formulir di React biasanya dimulai seperti ini:
const [name, setName] = useState(''); const [surname, setSurname] = useState(''); const [error, setError] = useState(null); function handleSubmit(event) { event.preventDefault(); }
✅ Ini sederhana — dan sangat baik untuk formulir kecil.
Tetapi begitu Anda meningkatkan skala, Anda akan tenggelam dalam hook state yang berulang, reset manual, dan panggilan event.preventDefault() yang tak ada habisnya.
Setiap ketukan tombol memicu render ulang, dan mengelola error atau state pending membutuhkan lebih banyak variabel state. Ini fungsional, tetapi jauh dari elegan.
Ketika formulir Anda bukan hanya satu komponen tetapi hierarki komponen bersarang, Anda akhirnya meneruskan props melalui setiap level:
<Form> <Field error={error} value={name} onChange={setName}> <Input /> </Field> </Form>
State, error, flag loading — semua dilewatkan melalui beberapa lapisan. 📉 \n Ini tidak hanya membuat kode menjadi berat tetapi membuat pemeliharaan dan refactoring menjadi menyakitkan. 😓
Pernahkah Anda mencoba mengimplementasikan update optimistik secara manual?
Itu ketika Anda menampilkan perubahan "sukses" di UI segera setelah tindakan pengguna — sebelum server benar-benar mengkonfirmasinya.
Kedengarannya mudah tetapi mengelola logika rollback ketika permintaan gagal bisa menjadi sakit kepala yang nyata. 🤕
Di mana Anda menyimpan state optimistik sementara? Bagaimana Anda menggabungkan dan kemudian memutar kembali? 🔄
React 19 memperkenalkan sesuatu yang jauh lebih bersih untuk ini.
Salah satu penambahan paling menarik di React 19 adalah hook ==*useActionState *==.
Ini menyederhanakan logika formulir dengan menggabungkan pengiriman formulir async, manajemen state, dan indikasi loading — semuanya dalam satu tempat. 🎯
const [state, actionFunction, isPending] = useActionState(fn, initialState);
Inilah yang terjadi:
==fn== — fungsi async Anda yang menangani pengiriman formulir
==initialState== — nilai awal dari state formulir Anda
==isPending== — flag bawaan yang menunjukkan apakah pengiriman sedang berlangsung
\
Fungsi async yang diteruskan ke ==useActionState== secara otomatis menerima dua argumen:
const action = async (previousState, formData) => { const message = formData.get('message'); try { await sendMessage(message); return { success: true, error: null }; } catch (error) { return { success: false, error }; } };
Anda kemudian menghubungkannya ke formulir Anda seperti ini:
const [state, actionFunction, isPending] = useActionState(action, { success: false, error: null, }); return <form action={actionFunction}> ... </form>;
\n Sekarang, ketika formulir dikirimkan, React secara otomatis:
Tidak perlu lagi ==useState, preventDefault,== atau logika reset manual — React mengurus semua itu. ⚙️
Jika Anda memutuskan untuk memicu tindakan formulir secara manual (misalnya, di luar prop action formulir), bungkus dengan ==startTransition==:
const handleSubmit = async (formData) => { await doSomething(); startTransition(() => { actionFunction(formData); }); };
Jika tidak, React akan memperingatkan Anda bahwa pembaruan async terjadi di luar transisi, dan ==isPending== tidak akan diperbarui dengan benar.
Logika formulir terasa deklaratif lagi — cukup jelaskan tindakannya, bukan pengkabelannya.
Hook baru yang kuat lainnya — ==useFormStatus== — menyelesaikan masalah prop drilling dalam pohon formulir.
import { useFormStatus } from 'react-dom'; const { pending, data, method, action } = useFormStatus();
Anda dapat memanggil hook ini di dalam komponen anak formulir mana pun, dan itu akan secara otomatis terhubung ke state formulir induk.
function SubmitButton() { const { pending, data } = useFormStatus(); const message = data ? data.get('message') : ''; return ( <button type="submit" disabled={pending}> {pending ? `Sending ${message}...` : 'Send'} </button> ); } function MessageForm() { return ( <form action={submitMessage}> <SubmitButton /> </form> ); }
:::info Perhatikan bahwa ==SubmitButton== dapat mengakses data formulir dan status pending — tanpa ada props yang diteruskan.
:::
🧩 Menghilangkan prop drilling dalam pohon formulir \n ⚡ Memungkinkan keputusan kontekstual di dalam komponen anak \n 💡 Menjaga komponen tetap terpisah dan lebih bersih
Akhirnya, mari kita bicarakan tentang salah satu penambahan favorit saya — ==useOptimistic==.
Ini membawa dukungan bawaan untuk pembaruan UI optimistik, membuat interaksi pengguna terasa instan dan lancar.
Bayangkan mengklik "Tambahkan ke favorit." Anda ingin menampilkan pembaruan segera — sebelum respons server.
Secara tradisional, Anda akan juggling antara state lokal, logika rollback, dan permintaan async.
Dengan ==useOptimistic==, ini menjadi deklaratif dan minimal:
const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [newMessage, ...state] ); const formAction = async (formData) => { addOptimisticMessage(formData.get('message')); try { await sendMessage(formData); } catch { console.error('Failed to send message'); } };
Jika permintaan server gagal, React secara otomatis kembali ke state sebelumnya.
Jika berhasil — perubahan optimistik tetap ada.
Fungsi pembaruan yang Anda berikan ke useOptimistic harus murni:
❌ Salah:
(prev, newTodo) => { prev.push(newTodo); return prev; }
✅ Benar:
(prev, newTodo) => [...prev, newTodo];
:::tip Selalu kembalikan objek atau array state yang baru!
:::
Jika Anda memicu pembaruan optimistik di luar action formulir, bungkus mereka dalam startTransition:
startTransition(() => { addOptimisticMessage(formData.get('message')); sendMessage(formData); });
Jika tidak, React akan memperingatkan Anda bahwa pembaruan optimistik terjadi di luar transisi. 💡


