Input tidak terkirim biasanya karena form ditulis di Server Component. Solusi yang jalan di App Router: buat file form terpisah dengan use client di baris pertama, simpan input di useState, lalu kirim lewat onSubmit dengan e.preventDefault().
"use client";
import { useState } from "react";
export default function ContactForm() {
const [nama, setNama] = useState("");
const [email, setEmail] = useState("");
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
console.log({ nama, email });
}
return (
<form onSubmit={handleSubmit}>
<input value={nama} onChange={(e) => setNama(e.target.value)} placeholder="Nama" />
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
<button type="submit">Kirim</button>
</form>
);
}
Bentuk Form Client Paling Sederhana yang Langsung Jalan
Form di atas disebut controlled input. Nilai input selalu mengikuti state React, jadi mudah divalidasi dan dikirim.
- Wajib jadi Client Component kalau memakai
useState,onChange, atauonSubmit. - Wajib jadi Client Component kalau membaca nilai input saat mengetik untuk validasi langsung.
- Boleh tetap Server Component kalau form hanya melempar ke Server Action tanpa state dan tanpa event browser.
Langkah Membuat Form Client Component di App Router
Simpan form interaktif misalnya di app/contact/ContactForm.tsx, lalu render dari page.tsx server.
1. Buat file baru dan tulis use client paling atas.
2. Tambah state untuk tiap field dan status kirim.
3. Hubungkan value dan onChange tiap input.
4. Buat handleSubmit dan cegah reload dengan preventDefault.
5. Tampilkan status sukses atau error di bawah tombol.
"use client";
import { useState } from "react";
export default function ContactForm() {
const [nama, setNama] = useState("");
const [email, setEmail] = useState("");
const [status, setStatus] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!nama || !email.includes("@")) {
setStatus("Isi nama dan email yang valid.");
return;
}
setStatus("Mengirim...");
await fetch("/api/contact", {
method: "POST",
body: JSON.stringify({ nama, email }),
});
setStatus("Terkirim.");
}
return (
<form onSubmit={handleSubmit}>
<input value={nama} onChange={(e) => setNama(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Kirim</button>
<p>{status}</p>
</form>
);
}
Perbandingan Pola Kirim Data dari Form
| Tujuan | Cocok dipakai | Catatan |
|---|---|---|
| Server Action | Mutasi langsung dari form | Validasi ulang input di server |
| Route Handler | Butuh respons JSON atau status HTTP | Gunakan method POST |
| Fetch API eksternal | Kirim ke layanan lain | Simpan secret hanya di server |
Jangan import modul server-only seperti akses database langsung ke file form client. Pindahkan logika itu ke Server Action atau Route Handler.
FAQ
Apakah semua form harus use client?
Tidak. Form yang butuh state, event, atau validasi saat mengetik harus client, form pasif bisa server.
Bagaimana validasi form tetap aman?
Lakukan cek cepat di client untuk UX, lalu validasi ulang semua input di server sebelum disimpan.
Kenapa onSubmit tidak jalan di Server Component?
Server Component tidak menjalankan event browser dan hook, jadi pindahkan form interaktif ke Client Component.