Kurnia Andi Nugroho
0 comment
Kalau kita bicara aplikasi web, hampir bisa dipastikan form adalah tulang punggungnya. Mulai dari login, registrasi, input data, sampai proses checkout—semuanya berputar di sekitar form.
Karena itulah, Livewire 4 memberikan perhatian besar pada urusan form. Bukan hanya soal input sederhana, tapi juga hal-hal yang lebih kompleks seperti validasi real-time, upload file, auto-save, hingga UX yang terasa responsif tanpa JavaScript berlebihan.
Mari kita bahas pelan-pelan, dari yang paling dasar.
Kita mulai dari contoh paling sederhana: form tambah post.
<?php // resources/views/components/post/⚡create.blade.php
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public $title = '';
public $content = '';
public function save()
{
Post::create(
$this->only(['title', 'content'])
);
session()->flash('status', 'Post berhasil disimpan.');
return $this->redirect('/posts');
}
};
?>
<form wire:submit="save">
<input type="text" wire:model="title">
<input type="text" wire:model="content">
<button type="submit">Save</button>
</form>
Di sini ada dua konsep penting:
wire:model Digunakan untuk mengikat input ke property Livewire. Ketika user mengetik, nilainya disimpan ke $title dan $content.wire:submit Digunakan untuk menangkap event submit dan memanggil method save().Tanpa AJAX, tanpa controller tambahan—langsung PHP.
Form tanpa validasi ibarat pintu rumah tanpa kunci. Livewire 4 mempermudah validasi dengan attribute #[Validate].
use Livewire\Attributes\Validate;
new class extends Component {
#[Validate('required')]
public $title = '';
#[Validate('required')]
public $content = '';
public function save()
{
$this->validate();
Post::create(
$this->only(['title', 'content'])
);
return $this->redirect('/posts');
}
};
Di Blade, kita tampilkan error-nya:
<input type="text" wire:model="title">
@error('title') <span>{{ $message }}</span> @enderror
<input type="text" wire:model="content">
@error('content') <span>{{ $message }}</span> @enderror
Sekarang, jika user men-submit form kosong, Livewire otomatis menampilkan pesan error.
Yang menarik: validasi ini berjalan di server, tapi terasa seperti di client.
Saat form mulai besar, property dan validasi akan memenuhi komponen. Untuk itulah Livewire menyediakan Form Object.
Buat form dengan Artisan:
php artisan livewire:form PostForm
Hasilnya:
namespace App\Livewire\Forms;
use Livewire\Form;
use Livewire\Attributes\Validate;
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
}
Gunakan di komponen:
use App\Livewire\Forms\PostForm;
new class extends Component {
public PostForm $form;
public function save()
{
$this->validate();
Post::create(
$this->form->only(['title', 'content'])
);
return $this->redirect('/posts');
}
};
Di Blade:
<input wire:model="form.title">
@error('form.title') <span>{{ $message }}</span> @enderror
<input wire:model="form.content">
@error('form.content') <span>{{ $message }}</span> @enderror
Dengan pendekatan ini:
Form object juga bisa menangani proses simpan:
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
public function store()
{
$this->validate();
Post::create(
$this->only(['title', 'content'])
);
}
}
Komponen jadi sangat ringkas:
public function save()
{
$this->form->store();
return $this->redirect('/posts');
}
Form object sangat cocok untuk create dan edit sekaligus.
class PostForm extends Form
{
public ?Post $post = null;
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
public function setPost(Post $post)
{
$this->post = $post;
$this->title = $post->title;
$this->content = $post->content;
}
public function update()
{
$this->validate();
$this->post->update(
$this->only(['title', 'content'])
);
}
}
Digunakan di post.edit:
public function mount(Post $post)
{
$this->form->setPost($post);
}
Setelah submit, sering kali kita ingin membersihkan form.
$this->reset();
$this->reset('title');
$this->reset(['title', 'content']);
Atau versi ringkas:
Post::create(
$this->pull()
);
pull() akan mengambil nilai sekaligus meresetnya.
Untuk kasus validasi kompleks:
use Illuminate\Validation\Rule;
protected function rules()
{
return [
'title' => [
'required',
Rule::unique('posts')->ignore($this->post),
],
'content' => 'required|min:5',
];
}
Dengan pendekatan ini, validasi hanya berjalan saat $this->validate() dipanggil.
Livewire otomatis men-disable input saat submit, tapi user tetap butuh feedback visual.
<button type="submit">
<span class="in-data-loading:hidden">Save</span>
<span class="not-in-data-loading:hidden">Saving...</span>
</button>
UX jadi jauh lebih jelas dan profesional.
<input wire:model.live="title">
Setiap ketikan langsung dikirim ke server. Cocok untuk:
Alternatif yang lebih hemat:
<input wire:model.blur="title">
Request hanya dikirim saat input kehilangan fokus.
Dengan .blur atau .live, Livewire otomatis memvalidasi input saat diubah.
#[Validate('required|min:5')]
public $title = '';
User langsung tahu kesalahan tanpa harus submit form.
updated()Untuk fitur auto-save:
public function updated($name, $value)
{
$this->post->update([
$name => $value,
]);
}
Setiap field disimpan begitu selesai diedit.
<input wire:model.blur="title" wire:dirty.class="border-yellow">
Atau:
<div wire:dirty wire:target="title">
Belum disimpan...
</div>
Ini detail kecil, tapi sangat meningkatkan UX.
Debounce (tunggu user berhenti mengetik):
<input wire:model.live.debounce.150ms="title">
Throttle (kirim request tiap interval):
<input wire:model.live.throttle.150ms="title">
Pilih sesuai kebutuhan performa aplikasi.
Daripada menulis ulang input + error:
<x-input-text name="title" wire:model="title" />
<!-- resources/views/components/input-text.blade.php -->
@props(['name'])
<input type="text" {{ $attributes }}>
@error($name) <span>{{ $message }}</span> @enderror
Form jadi lebih bersih dan konsisten.
Contoh input counter:
<x-input-counter wire:model="quantity" />
<div x-data="{ count: 0 }"
x-modelable="count"
{{ $attributes }}>
<button @click="count--">-</button>
<span x-text="count"></span>
<button @click="count++">+</button>
</div>
x-modelable adalah kunci agar Alpine dan Livewire bisa berbagi state.
Form di Livewire 4 bukan sekadar input dan submit. Ia adalah sistem yang lengkap:
Kurnia Andi Nugroho
Web & Mobile App Developer, Laravel, Inertia, Vue.Js, React.Js
Founder of Lagikoding.com Laravel Enthusiast & Web Developer