Pendahuluan
“Fitur pencarian real-time? Oke, berarti kita harus setup React/Vue dulu…”
Tidak harus! Jika aplikasimu adalah Laravel Blade yang sudah berjalan dan kamu hanya butuh beberapa halaman interaktif, mengintegrasikan SPA framework terasa berlebihan. Perlu belajar JavaScript framework baru, setup build tool, handle state management…
Livewire hadir sebagai solusi elegant: buat komponen reaktif dengan PHP murni. Tidak ada JavaScript yang perlu ditulis — reactivity-nya diurus Livewire di balik layar.
Cara kerja Livewire:
1. User ketik di input
2. Livewire kirim AJAX ke server (otomatis)
3. PHP component update state
4. Livewire update DOM secara efisien (diff)
5. User melihat perubahan — tanpa refresh halaman
Livewire 3 (dirilis 2023) adalah versi terbaru dengan performa jauh lebih baik dan sintaks yang lebih bersih.
Apa itu Livewire?
Livewire adalah full-stack framework untuk Laravel yang memungkinkan kamu membangun UI dinamis menggunakan PHP dan Blade. Tidak perlu API endpoints terpisah, tidak perlu state management JavaScript.
Kapan gunakan Livewire:
- Form dengan validasi real-time
- Pencarian/filter yang langsung update
- Tabel dengan pagination AJAX
- Modal interaktif
- Shopping cart sederhana
Kapan lebih baik gunakan React/Vue:
- Aplikasi sangat complex dengan state management mendalam
- Perlu offline capability
- Mobile app (React Native)
- Tim sudah expert di JavaScript framework
Langkah 1: Instalasi Livewire 3
composer require livewire/livewire
Di layout utama, tambahkan directive Livewire:
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title') | KamusNgoding</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles {{-- ← Tambahkan di dalam <head> --}}
</head>
<body>
@yield('content')
@livewireScripts {{-- ← Tambahkan sebelum </body> --}}
</body>
</html>
Catatan Laravel 11+: Jika menggunakan
@vitedengan Livewire terbaru, script dan style sudah ter-inject otomatis. Cek dokumentasi versi Livewire yang kamu pakai.
Langkah 2: Komponen Livewire Pertama
php artisan make:livewire Counter
Perintah ini membuat 2 file:
app/Livewire/Counter.php— Logika (PHP class)resources/views/livewire/counter.blade.php— Tampilan (Blade view)
<?php
// app/Livewire/Counter.php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
// Properti public = reaktif (otomatis sync dengan view)
public int $count = 0;
public function increment(): void
{
$this->count++;
}
public function decrement(): void
{
$this->count--;
}
public function render()
{
return view('livewire.counter');
}
}
{{-- resources/views/livewire/counter.blade.php --}}
<div> {{-- Root element wajib ada satu --}}
<h2 class="text-2xl font-bold">{{ $count }}</h2>
<button wire:click="increment" class="bg-blue-500 text-white px-4 py-2 rounded">
+
</button>
<button wire:click="decrement" class="bg-red-500 text-white px-4 py-2 rounded">
-
</button>
</div>
Gunakan komponen ini di halaman Blade manapun:
{{-- resources/views/home.blade.php --}}
@extends('layouts.app')
@section('content')
<livewire:counter />
{{-- atau: @livewire('counter') --}}
@endsection
wire:click="increment" — saat tombol diklik, Livewire kirim AJAX ke server, memanggil method increment(), lalu update view secara otomatis.
Langkah 3: Real-time Search Filter
Ini adalah use case paling populer Livewire. Kita buat komponen pencarian task yang update saat user mengetik:
<?php
// app/Livewire/TaskSearch.php
namespace App\Livewire;
use App\Models\Task;
use Livewire\Component;
use Livewire\WithPagination;
class TaskSearch extends Component
{
use WithPagination;
// wire:model.live akan update ini setiap user mengetik
public string $search = '';
public string $status = '';
public string $sortBy = 'created_at';
// Reset halaman ke 1 saat filter berubah
public function updatingSearch(): void
{
$this->resetPage();
}
public function updatingStatus(): void
{
$this->resetPage();
}
public function render()
{
$tasks = Task::query()
->where('user_id', auth()->id())
->when($this->search, fn($q) =>
$q->where('title', 'like', "%{$this->search}%")
)
->when($this->status, fn($q) =>
$q->where('status', $this->status)
)
->orderBy($this->sortBy, 'desc')
->paginate(10);
return view('livewire.task-search', compact('tasks'));
}
}
{{-- resources/views/livewire/task-search.blade.php --}}
<div>
{{-- Filter section --}}
<div class="flex gap-4 mb-4">
<input
wire:model.live.debounce.300ms="search"
type="text"
placeholder="Cari task..."
class="border rounded px-3 py-2 flex-1"
/>
<select wire:model.live="status" class="border rounded px-3 py-2">
<option value="">Semua Status</option>
<option value="pending">Pending</option>
<option value="completed">Selesai</option>
</select>
</div>
{{-- Loading indicator --}}
<div wire:loading class="text-gray-500 text-sm mb-2">
Mencari...
</div>
{{-- Hasil pencarian --}}
<div wire:loading.remove>
@forelse($tasks as $task)
<div class="border rounded p-4 mb-2">
<h3 class="font-semibold">{{ $task->title }}</h3>
<span class="text-sm text-gray-500">{{ $task->status }}</span>
</div>
@empty
<p class="text-gray-500">Tidak ada task yang ditemukan.</p>
@endforelse
{{-- Pagination Livewire --}}
{{ $tasks->links() }}
</div>
</div>
wire:model.live.debounce.300ms — Update server setelah user berhenti mengetik 300ms. Mencegah terlalu banyak request saat user sedang mengetik.
Langkah 4: Form Handling & Validasi Real-time
Livewire 3 memperkenalkan Form Objects — cara bersih untuk mengelola form kompleks:
php artisan make:livewire-form TaskForm
<?php
// app/Livewire/Forms/TaskForm.php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class TaskForm extends Form
{
#[Validate('required|min:3|max:255')]
public string $title = '';
#[Validate('required|in:low,medium,high')]
public string $priority = 'medium';
#[Validate('nullable|date|after:today')]
public ?string $due_date = null;
}
<?php
// app/Livewire/CreateTask.php
namespace App\Livewire;
use App\Livewire\Forms\TaskForm;
use App\Models\Task;
use Livewire\Component;
class CreateTask extends Component
{
public TaskForm $form;
public function save(): void
{
// Validasi semua field sekaligus
$this->form->validate();
Task::create([
'user_id' => auth()->id(),
'title' => $this->form->title,
'priority' => $this->form->priority,
'due_date' => $this->form->due_date,
]);
// Reset form
$this->form->reset();
// Kirim event ke browser (untuk notifikasi)
$this->dispatch('task-created');
}
public function render()
{
return view('livewire.create-task');
}
}
{{-- resources/views/livewire/create-task.blade.php --}}
<div>
<form wire:submit="save">
<div class="mb-4">
<label>Judul Task</label>
<input
wire:model.live="form.title"
type="text"
class="border rounded px-3 py-2 w-full @error('form.title') border-red-500 @enderror"
/>
@error('form.title')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="mb-4">
<label>Prioritas</label>
<select wire:model="form.priority" class="border rounded px-3 py-2 w-full">
<option value="low">Rendah</option>
<option value="medium">Sedang</option>
<option value="high">Tinggi</option>
</select>
@error('form.priority')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<button type="submit" class="bg-green-600 text-white px-6 py-2 rounded"
wire:loading.attr="disabled">
<span wire:loading.remove>Simpan Task</span>
<span wire:loading>Menyimpan...</span>
</button>
</form>
</div>
wire:model.live pada judul akan validasi setiap kali user mengetik — error langsung muncul tanpa submit form. Sangat meningkatkan UX!
Langkah 5: Event & Komunikasi Antar Komponen
Jika ada dua komponen di halaman yang sama (misal: CreateTask dan TaskSearch), mereka bisa berkomunikasi via event:
// Di CreateTask.php, setelah save():
$this->dispatch('task-created'); // Broadcast event
// Di TaskSearch.php, dengarkan event:
#[On('task-created')]
public function refreshList(): void
{
$this->resetPage(); // Refresh hasil pencarian
}
{{-- Halaman yang menggunakan kedua komponen --}}
<livewire:create-task />
<livewire:task-search />
Saat task baru dibuat di CreateTask, TaskSearch otomatis refresh list — tanpa JavaScript apapun!
Langkah 6: Loading State & Optimistic UI
{{-- Tombol dengan loading state --}}
<button wire:click="delete({{ $task->id }})" class="text-red-500">
<span wire:loading.remove wire:target="delete({{ $task->id }})">Hapus</span>
<span wire:loading wire:target="delete({{ $task->id }})">
<svg class="animate-spin h-4 w-4" ...></svg>
</span>
</button>
{{-- Blur tabel saat loading --}}
<div wire:loading.class="opacity-50 pointer-events-none">
{{-- Isi tabel --}}
</div>
{{-- Tampilkan overlay global saat ada request --}}
<div wire:loading.delay.long class="fixed inset-0 bg-black bg-opacity-25 flex items-center justify-center">
<div class="bg-white rounded p-4">Loading...</div>
</div>
wire:loading.delay.long — hanya tampilkan loading overlay jika request memakan waktu lebih dari 1 detik. Mencegah “flicker” untuk operasi cepat.
Contoh Proyek: Task Manager Lengkap dengan Livewire
Konversi Task Manager dari artikel CRUD sebelumnya menjadi full Livewire:
<?php
// app/Livewire/TaskManager.php
namespace App\Livewire;
use App\Models\Task;
use Livewire\Component;
use Livewire\WithPagination;
class TaskManager extends Component
{
use WithPagination;
public string $search = '';
public string $filter = '';
public bool $showForm = false;
public ?int $editId = null;
public string $title = '';
public string $priority = 'medium';
public function openCreate(): void
{
$this->editId = null;
$this->title = '';
$this->priority = 'medium';
$this->showForm = true;
}
public function openEdit(Task $task): void
{
$this->editId = $task->id;
$this->title = $task->title;
$this->priority = $task->priority;
$this->showForm = true;
}
public function save(): void
{
$validated = $this->validate([
'title' => 'required|min:3',
'priority' => 'required|in:low,medium,high',
]);
if ($this->editId) {
Task::find($this->editId)->update($validated);
} else {
Task::create([...$validated, 'user_id' => auth()->id()]);
}
$this->showForm = false;
$this->reset(['title', 'priority', 'editId']);
}
public function delete(Task $task): void
{
$task->delete();
}
public function toggleComplete(Task $task): void
{
$task->update(['status' => $task->status === 'completed' ? 'pending' : 'completed']);
}
public function render()
{
$tasks = Task::query()
->where('user_id', auth()->id())
->when($this->search, fn($q) => $q->where('title', 'like', "%{$this->search}%"))
->when($this->filter, fn($q) => $q->where('status', $this->filter))
->latest()
->paginate(10);
return view('livewire.task-manager', compact('tasks'));
}
}
Troubleshooting: Error yang Sering Muncul
Livewire component tidak muncul atau blank
Penyebab: @livewireScripts belum ditambahkan di layout, atau class component tidak ditemukan.
Solusi:
{{-- Pastikan ada di layout --}}
@livewireStyles {{-- di <head> --}}
@livewireScripts {{-- sebelum </body> --}}
{{-- Atau dengan full script tag --}}
<livewire:scripts />
# Jika class tidak ditemukan
composer dump-autoload
php artisan livewire:discover
Properti tidak ter-update setelah input
Penyebab: Menggunakan wire:model tanpa .live — update hanya terjadi saat form di-submit.
Solusi:
{{-- Update setiap keystroke: --}}
<input wire:model.live="search" />
{{-- Update dengan debounce (lebih efisien): --}}
<input wire:model.live.debounce.300ms="search" />
{{-- Update hanya saat blur (keluar dari input): --}}
<input wire:model.blur="search" />
Alpine.js conflict — $wire is not defined
Penyebab: Livewire 3 sudah bundle Alpine.js. Jika Alpine.js diimport lagi secara manual, terjadi konflik.
Solusi:
// resources/js/app.js
// HAPUS import Alpine ini jika ada:
// import Alpine from 'alpinejs'
// window.Alpine = Alpine
// Alpine.start()
// Livewire 3 sudah handle Alpine — tidak perlu import manual
npm run build
Pertanyaan yang Sering Diajukan
Livewire vs Inertia.js, kapan menggunakan yang mana?
Livewire tepat jika kamu sudah comfortable dengan Blade dan tidak ingin berurusan dengan JavaScript framework. Cocok untuk admin panel, halaman dengan banyak form. Inertia.js tepat jika timmu sudah mahir React/Vue dan ingin membangun SPA yang lebih complex dengan routing client-side.
Apakah Livewire cocok untuk aplikasi berskala besar?
Ya, tapi ada pertimbangannya. Setiap interaksi Livewire mengirim AJAX request ke server — ini fine untuk user biasa, tapi bisa jadi bottleneck dengan banyak user simultan. Optimasi dengan wire:model.blur (hanya update saat blur) dan lazy loading bisa mengurangi jumlah request secara signifikan.
Bagaimana performance Livewire dibanding SPA (React/Vue)?
Untuk halaman sederhana, perbedaannya tidak signifikan. React/Vue lebih cepat untuk interaksi yang sangat frequent (seperti drag-and-drop) karena semua di client-side. Livewire lebih lambat karena setiap update perlu round-trip ke server, tapi untuk kebanyakan use case (form, search, CRUD), ini tidak terasa.
Bisakah Livewire digunakan bersamaan dengan Alpine.js custom?
Ya! Livewire 3 bundle Alpine.js secara otomatis. Kamu bisa gunakan Alpine.js untuk interaksi ringan di client-side (toggle, dropdown) tanpa konflik. Cukup pastikan tidak import Alpine.js secara terpisah di file JavaScript-mu.
Kesimpulan
Dengan Livewire 3, kamu bisa membangun UI yang biasanya butuh React atau Vue — hanya dengan PHP dan Blade. Tidak ada JavaScript framework baru yang perlu dipelajari.
Kita sudah belajar:
- Membuat komponen reaktif dengan
wire:modeldanwire:click - Real-time search dengan debounce
- Form handling dengan validasi langsung
- Komunikasi antar komponen via event
Selanjutnya, pelajari cara mengoptimalkan query dan caching di artikel Performance Optimization — karena setiap request Livewire menyentuh database, query yang efisien menjadi sangat penting!
Livewire adalah salah satu alasan mengapa membangun aplikasi web dengan Laravel sangat menyenangkan. Selamat bereksperimen! ⚡