ReactJS #20: Form dan Validasi
Mengirim Data ke API (POST Data dari Pengguna)
Halo, pengirim data! 📨
Selama ini kita hanya mengambil data dari API. Sekarang saatnya belajar mengirim data dari pengguna ke server. Ibaratnya, kita belajar mengisi formulir pendaftaran lalu mengirimkannya lewat pos. Di React, kita akan membuat form yang bisa menerima input, memvalidasinya (memeriksa apakah isian sudah benar), lalu mengirimnya ke API dengan metode POST.
💡 Analogi: Bayangkan kamu ingin mendaftar perpustakaan. Kamu mengisi formulir (nama, alamat, nomor telepon). Sebelum dikirim, petugas memeriksa apakah semua kolom sudah diisi dengan benar (validasi). Lalu formulir dimasukkan ke dalam amplop dan dikirim ke petugas (POST ke server). Server membalas dengan kartu anggota baru (response).
🔍 Konsep Penting: Controlled Components
Di React, cara standar membuat form adalah dengan controlled components. Artinya, nilai input dikendalikan oleh state React. Setiap kali pengguna mengetik, state diperbarui, dan nilai input selalu mengikuti state. Ini memudahkan kita untuk membaca dan memvalidasi data.
Contoh sederhana input teks:
const [nama, setNama] = useState('');
<input
type="text"
value={nama}
onChange={(e) => setNama(e.target.value)}
/>
🧱 Langkah 1: Membuat Form Sederhana
Kita akan membuat form untuk menambahkan postingan baru ke API JSONPlaceholder. API yang digunakan: https://jsonplaceholder.typicode.com/posts dengan metode POST.
Buat komponen FormTambahPostingan.js:
import { useState } from 'react';
function FormTambahPostingan() {
// State untuk menyimpan data form
const [formData, setFormData] = useState({
title: '',
body: '',
userId: 1
});
// State untuk loading dan response
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
// State untuk validasi (error per field)
const [errors, setErrors] = useState({});
// Fungsi untuk menangani perubahan input
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
// Hapus error untuk field ini saat diketik ulang
if (errors[name]) {
setErrors({
...errors,
[name]: null
});
}
};
// Fungsi validasi
const validate = () => {
const newErrors = {};
if (!formData.title.trim()) {
newErrors.title = 'Judul tidak boleh kosong';
}
if (!formData.body.trim()) {
newErrors.body = 'Isi postingan tidak boleh kosong';
}
return newErrors;
};
// Fungsi handle submit
const handleSubmit = async (e) => {
e.preventDefault(); // mencegah reload halaman
// Validasi
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
setLoading(true);
setError(null);
setResponse(null);
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (!res.ok) {
throw new Error('Gagal mengirim data');
}
const data = await res.json();
setResponse(data);
// Reset form setelah sukses
setFormData({ title: '', body: '', userId: 1 });
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="form-demo">
<h3>📝 Tambah Postingan Baru</h3>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Judul:</label>
<input
type="text"
name="title"
value={formData.title}
onChange={handleChange}
placeholder="Masukkan judul..."
/>
{errors.title && <div className="error-message">{errors.title}</div>}
</div>
<div className="form-group">
<label>Isi:</label>
<textarea
name="body"
value={formData.body}
onChange={handleChange}
placeholder="Tulis isi postingan..."
rows="5"
/>
{errors.body && <div className="error-message">{errors.body}</div>}
</div>
<button type="submit" className="submit-btn" disabled={loading}>
{loading ? '⏳ Mengirim...' : '🚀 Kirim Postingan'}
</button>
</form>
{/* Tampilkan loading spinner */}
{loading && (
<div className="spinner"></div>
)}
{/* Tampilkan pesan sukses */}
{response && (
<div style={{ marginTop: '30px', backgroundColor: '#d4edda', padding: '20px', borderRadius: '50px' }}>
<p>✅ Postingan berhasil dikirim!</p>
<p><strong>ID:</strong> {response.id}</p>
<p><strong>Judul:</strong> {response.title}</p>
</div>
)}
{/* Tampilkan pesan error */}
{error && (
<div style={{ marginTop: '30px', backgroundColor: '#f8d7da', padding: '20px', borderRadius: '50px', color: '#721c24' }}>
❌ {error}
</div>
)}
</div>
);
}
export default FormTambahPostingan;
Penjelasan kode:
- State
formData: menyimpan nilai input (title, body, userId).
- handleChange: memperbarui state saat pengguna mengetik, sekaligus menghapus pesan error untuk field yang diperbaiki.
- validate: memeriksa apakah field tidak kosong. Bisa ditambah validasi lain (misal email).
- handleSubmit: dipanggil saat form dikirim. Mencegah reload, validasi, lalu mengirim data dengan
fetch POST.
- fetch: menggunakan method POST, headers
Content-Type: application/json, dan body diubah ke JSON.
- Setelah sukses, response ditampilkan dan form direset.
- Loading dan error handling seperti biasa.
📧 Contoh Validasi Email
Kita bisa menambahkan validasi lebih lanjut, misalnya untuk field email. Tambahkan field email di state dan form, lalu periksa format email dengan regex sederhana.
// Di dalam state formData tambahkan email: ''
// Di dalam fungsi validate:
if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Format email tidak valid';
}
🧪 Langkah 2: Menggabungkan ke App.js
Sekarang kita panggil komponen form di App.js:
import FormTambahPostingan from './FormTambahPostingan';
function App() {
return (
<div className="App">
<h1>📬 Form dan Validasi</h1>
<FormTambahPostingan />
</div>
);
}
🧩 Memahami Alur POST
Saat kita mengirim data dengan POST, beberapa hal terjadi:
- Browser mengirim permintaan ke server dengan data di body.
- Server memproses data, biasanya menyimpannya ke database.
- Server mengirim respons, biasanya berupa data yang sudah disimpan (termasuk ID baru).
- Kita bisa menggunakan respons tersebut untuk memberi tahu pengguna bahwa data berhasil dikirim.
Di JSONPlaceholder, data tidak benar-benar disimpan, tapi server akan mengembalikan data yang kita kirim dengan ID baru (biasanya 101). Cocok untuk latihan.
💡 Catatan: API sungguhan mungkin memerlukan autentikasi (token) dan struktur data yang berbeda. Tapi prinsipnya sama: mengirim data dengan method POST.
🎯 Latihan: Form Komentar
Buat komponen FormKomentar dengan field: nama, email, komentar. Kirim ke API https://jsonplaceholder.typicode.com/comments dengan method POST. Struktur data yang diharapkan: { name: string, email: string, body: string, postId: 1 } (postId bisa diisi 1). Tambahkan validasi:
- Nama tidak boleh kosong.
- Email harus valid (gunakan regex sederhana).
- Komentar tidak boleh kosong, minimal 10 karakter.
Tampilkan pesan error spesifik untuk setiap field. Setelah sukses, tampilkan data komentar yang dikirim dan reset form.
⚠️ Peringatan: JSONPlaceholder akan menerima POST dan mengembalikan data dengan ID baru, tapi data tidak benar-benar tersimpan. Untuk latihan, ini sudah cukup.
📌 Tips Tambahan
- Reset form setelah submit: gunakan
setFormData({ ...initialState }).
- Nonaktifkan tombol saat loading:
disabled={loading} agar tidak diklik ganda.
- Gunakan
e.preventDefault() untuk mencegah reload.
- Validasi bisa dilakukan di sisi klien (React) dan sisi server. Jangan pernah percaya hanya pada validasi klien.
🚀 Kesimpulan
- Controlled components adalah cara standar mengelola form di React dengan state.
- Validasi dapat dilakukan sebelum mengirim data, memberikan umpan balik langsung ke pengguna.
- POST request dengan fetch memerlukan method POST, headers, dan body yang di-stringify.
- Selalu handle loading, error, dan response sukses untuk UX yang baik.
Di artikel selanjutnya kita akan belajar tentang useContext untuk berbagi data antar komponen tanpa mengirim props berulang-ulang. Sampai jumpa!