Input Tidak Terkirim? Cara Membuat Form Client Component Next Js yang Jalan

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, atau onSubmit.
  • 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.