Tutorial Laravel 12 API Next JS 15 dan Tailwind CSS #14 Product Form

Belajar membangun aplikasi fullstack modern dengan backend Laravel 12 RESTful API, frontend Next.js, dan desain menggunakan Tailwind CSS. Tutorial ini membahas step-by-step mulai dari setup environment, pembuatan API, konsumsi API di Next.js, hingga integrasi UI responsif.

✅ Telah dilihat 236 kali

Rating: 5.00 ⭐

... 13 August 2025, 20:33

Konfigurasi Product Form

Silakan teman-teman buka file ProductForm.tsx kemudian sesuaikan menjadi seperti berikut ini:

components/
├── products/
    ├── Breadcrumbs.tsx
    ├── DeleteConfirmDialog.tsx
    ├── ProductForm.tsx <-----

Kode Lengkap

"use client"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { ProductPayload } from "@/services/products"

export function ProductForm({
    form,
    setForm,
    errors,
    isSubmitting,
    onSubmit,
}: {
    form: ProductPayload
    setForm: (form: ProductPayload) => void
    errors: Record<string, string>
    isSubmitting: boolean
    onSubmit: (e: React.FormEvent) => void
}) {
    return (
        <form onSubmit={onSubmit} className="space-y-4">
            <div>
                <Label htmlFor="name">Nama Produk</Label>
                <Input id="name" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
                {errors.name && <p className="text-red-500 text-sm">{errors.name}</p>}
            </div>
            <div>
                <Label htmlFor="description">Deskripsi</Label>
                <Input id="description" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} />
            </div>
            <div>
                <Label htmlFor="price">Harga</Label>
                <Input
                    id="price"
                    type="number"
                    value={form.price === 0 ? "" : form.price}
                    onChange={(e) => setForm({ ...form, price: Number(e.target.value) || 0 })}
                />
                {errors.price && <p className="text-red-500 text-sm">{errors.price}</p>}
            </div>
            <div>
                <Label htmlFor="stock">Stok</Label>
                <Input
                    id="stock"
                    type="number"
                    value={form.stock === 0 ? "" : form.stock}
                    onChange={(e) => setForm({ ...form, stock: Number(e.target.value) || 0 })}
                />
                {errors.stock && <p className="text-red-500 text-sm">{errors.stock}</p>}
            </div>
            <Button type="submit" disabled={isSubmitting}>
                {isSubmitting ? "Menyimpan..." : "Simpan"}
            </Button>
        </form>
    )
}

use client

"use client"
  • Menandakan bahwa ProductForm adalah Client Component.
  • Kenapa? Karena form ini berinteraksi dengan user (input teks, angka, submit form).
  • Kalau tidak pakai use client, maka event seperti onChange atau onSubmit tidak akan bisa berjalan.

Import

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { ProductPayload } from "@/services/products"
  • Button → komponen tombol dari shadcn/ui.
  • Input → komponen input field dengan styling konsisten.
  • Label → label untuk tiap input, supaya lebih jelas dan accessible.
  • ProductPayload → tipe data produk (biasanya: { name, description, price, stock }) yang diambil dari services/products.

Props Komponen

Komponen ProductForm menerima props berikut:

  • form: ProductPayload → state/form data produk (nama, deskripsi, harga, stok).
  • setForm: (form: ProductPayload) => void → fungsi untuk mengubah state form ketika user mengetik.
  • errors: Record<string, string> → objek untuk menampilkan pesan error per field (misalnya validasi gagal).
  • isSubmitting: boolean → flag untuk menandai apakah form sedang diproses (loading/saving).
  • onSubmit: (e: React.FormEvent) => void → fungsi yang dipanggil ketika form disubmit.

Struktur Form

<form onSubmit={onSubmit} className="space-y-4">
  {/* Input Nama Produk */}
  <div>
    <Label htmlFor="name">Nama Produk</Label>
    <Input id="name" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
    {errors.name && <p className="text-red-500 text-sm">{errors.name}</p>}
  </div>
  
  {/* Input Deskripsi */}
  <div>
    <Label htmlFor="description">Deskripsi</Label>
    <Input id="description" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} />
  </div>
  
  {/* Input Harga */}
  <div>
    <Label htmlFor="price">Harga</Label>
    <Input
      id="price"
      type="number"
      value={form.price === 0 ? "" : form.price}
      onChange={(e) => setForm({ ...form, price: Number(e.target.value) || 0 })}
    />
    {errors.price && <p className="text-red-500 text-sm">{errors.price}</p>}
  </div>
  
  {/* Input Stok */}
  <div>
    <Label htmlFor="stock">Stok</Label>
    <Input
      id="stock"
      type="number"
      value={form.stock === 0 ? "" : form.stock}
      onChange={(e) => setForm({ ...form, stock: Number(e.target.value) || 0 })}
    />
    {errors.stock && <p className="text-red-500 text-sm">{errors.stock}</p>}
  </div>
  
  {/* Tombol Submit */}
  <Button type="submit" disabled={isSubmitting}>
    {isSubmitting ? "Menyimpan..." : "Simpan"}
  </Button>
</form>
  • Label + Input → setiap field ada label dan input supaya jelas.
  • Error Message → ditampilkan di bawah input kalau ada error validasi.
  • Harga & Stok → diatur sebagai angka (type="number"), default jadi kosong kalau 0.
  • Tombol Submit → berubah teks jadi "Menyimpan..." ketika sedang proses submit.

Kesimpulan

ProductForm.tsx adalah komponen reusable untuk input data produk. Dengan ini kita bisa:

  1. Tambah produk baru.
  2. Edit produk yang sudah ada.
  3. Validasi input (via errors).
  4. Kendalikan status loading dengan isSubmitting.

🔥 Flash Sale


📜 Table Of Contents


📌 Daftar Episode


Daftar eBook