Kuasai Livewire dari dasar. Pelajari mengapa Livewire diciptakan, pahami core concepts seperti components, data binding, events, lifecycle hooks, dan validation. Bangun complete production-ready task management app dengan Livewire yang cover semua fundamentals dengan best practices.

Livewire revolutionize Laravel development dengan enable developers untuk build interactive, reactive user interfaces tanpa write JavaScript. Instead of manually manage AJAX requests dan DOM updates, Livewire handle complexity behind the scenes. Tapi mengapa Livewire ada, dan apa yang membuatnya fundamentally different?
Dalam artikel ini, kita akan explore Livewire's philosophy, understand mengapa Livewire diciptakan, dive deep ke core concepts, dan build complete production-ready task management application yang demonstrate semua fundamental Livewire patterns.
Sebelum Livewire, Laravel developers faced significant challenges:
Caleb Porzio created Livewire di 2019 dengan revolutionary approach:
Livewire components adalah PHP classes yang manage state dan handle user interactions.
<?php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public int $count = 0;
public function increment(): void
{
$this->count++;
}
public function decrement(): void
{
$this->count--;
}
public function render()
{
return view('livewire.counter');
}
}Two-way data binding automatically syncs data antara component dan view.
<div>
<h1>Counter: {{ $count }}</h1>
<!-- wire:model creates two-way binding -->
<input type="text" wire:model="name" placeholder="Enter name" />
<p>Hello, {{ $name }}!</p>
<!-- Debounce updates untuk performance -->
<input type="text" wire:model.debounce-500ms="email" />
<!-- Lazy updates on blur -->
<input type="text" wire:model.lazy="phone" />
<!-- Throttle updates -->
<input type="text" wire:model.throttle-1000ms="search" />
</div>Handle user interactions dengan call component methods.
<?php
namespace App\Livewire;
use Livewire\Component;
class TodoList extends Component
{
public array $todos = [];
public string $newTodo = '';
public function addTodo(): void
{
if (empty($this->newTodo)) {
return;
}
$this->todos[] = [
'id' => uniqid(),
'title' => $this->newTodo,
'completed' => false,
];
$this->newTodo = '';
}
public function toggleTodo(string $id): void
{
foreach ($this->todos as &$todo) {
if ($todo['id'] === $id) {
$todo['completed'] = !$todo['completed'];
break;
}
}
}
public function deleteTodo(string $id): void
{
$this->todos = array_filter(
$this->todos,
fn($todo) => $todo['id'] !== $id
);
}
public function render()
{
return view('livewire.todo-list');
}
}Livewire provide hooks untuk run code pada specific times dalam component's life.
<?php
namespace App\Livewire;
use Livewire\Component;
class UserProfile extends Component
{
public string $userId;
public array $user = [];
// Runs ketika component instantiated
public function mount(string $userId): void
{
$this->userId = $userId;
$this->loadUser();
}
// Runs sebelum rendering
public function hydrate(): void
{
// Refresh data jika needed
}
// Runs setelah rendering
public function rendered(): void
{
// Dispatch events, log, etc.
}
// Runs sebelum update property
public function updating(string $name, mixed $value): void
{
// Validate atau transform value
}
// Runs setelah update property
public function updated(string $name, mixed $value): void
{
if ($name === 'email') {
$this->validateEmail($value);
}
}
private function loadUser(): void
{
$this->user = [
'id' => $this->userId,
'name' => 'John Doe',
'email' => 'john@example.com',
];
}
public function render()
{
return view('livewire.user-profile');
}
}Validate user input dengan Laravel's validation rules.
<?php
namespace App\Livewire;
use Livewire\Component;
class ContactForm extends Component
{
public string $name = '';
public string $email = '';
public string $message = '';
// Define validation rules
protected array $rules = [
'name' => 'required|min:3|max:50',
'email' => 'required|email',
'message' => 'required|min:10|max:1000',
];
// Custom error messages
protected array $messages = [
'name.required' => 'Please enter your name',
'email.email' => 'Please enter a valid email',
];
public function submit(): void
{
// Validate semua properties
$this->validate();
// Process form
// ...
$this->reset();
session()->flash('success', 'Message sent successfully!');
}
// Validate single property
public function updatedEmail(): void
{
$this->validateOnly('email');
}
public function render()
{
return view('livewire.contact-form');
}
}Cache expensive calculations dan recompute ketika dependencies change.
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\Attributes\Computed;
class ShoppingCart extends Component
{
public array $items = [];
// Computed property - cached sampai dependencies change
#[Computed]
public function total()
{
return collect($this->items)
->sum(fn($item) => $item['price'] * $item['quantity']);
}
#[Computed]
public function itemCount()
{
return collect($this->items)
->sum(fn($item) => $item['quantity']);
}
public function addItem(array $item): void
{
$this->items[] = $item;
// total() dan itemCount() akan recomputed
}
public function render()
{
return view('livewire.shopping-cart');
}
}Communicate antara components menggunakan events.
<?php
namespace App\Livewire;
use Livewire\Component;
class NotificationButton extends Component
{
public function notify(): void
{
// Dispatch event ke other components
$this->dispatch('notification-sent', message: 'Hello!');
}
public function render()
{
return view('livewire.notification-button');
}
}
// Listening component
class NotificationCenter extends Component
{
public array $notifications = [];
// Listen untuk events
protected $listeners = [
'notification-sent' => 'addNotification',
];
public function addNotification(string $message): void
{
$this->notifications[] = [
'id' => uniqid(),
'message' => $message,
'timestamp' => now(),
];
}
public function render()
{
return view('livewire.notification-center');
}
}Show/hide elements berdasarkan component state.
<div>
<!-- Simple if -->
@if ($isLoggedIn)
<p>Welcome, {{ $userName }}!</p>
@else
<p>Please log in</p>
@endif
<!-- If/else if/else -->
@if ($status === 'pending')
<span class="badge badge-warning">Pending</span>
@elseif ($status === 'completed')
<span class="badge badge-success">Completed</span>
@else
<span class="badge badge-danger">Failed</span>
@endif
<!-- Unless (opposite of if) -->
@unless ($isAdmin)
<p>You don't have admin access</p>
@endunless
<!-- Ternary-like dengan isset -->
@isset($user)
<p>User: {{ $user->name }}</p>
@endisset
</div>Render lists efficiently dengan proper keys.
<div>
<!-- Basic loop -->
@foreach ($items as $item)
<div>{{ $item['name'] }}</div>
@endforeach
<!-- Loop dengan index -->
@foreach ($items as $index => $item)
<div>{{ $index + 1 }}. {{ $item['name'] }}</div>
@endforeach
<!-- Loop dengan $loop variable -->
@foreach ($items as $item)
<div>
@if ($loop->first)
<strong>First item</strong>
@endif
{{ $item['name'] }}
@if ($loop->last)
<strong>Last item</strong>
@endif
</div>
@endforeach
<!-- Empty state -->
@forelse ($items as $item)
<div>{{ $item['name'] }}</div>
@empty
<p>No items found</p>
@endforelse
</div>Style components dengan scoped CSS.
<div class="card">
<h2>{{ $title }}</h2>
<p>{{ $description }}</p>
</div>
<style scoped>
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 20px;
transition: all 0.2s;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-4px);
}
h2 {
margin: 0 0 10px 0;
font-size: 1.25rem;
}
p {
margin: 0;
color: #666;
}
</style>Mari build complete task management application dengan Livewire.
laravel-app/
├── app/
│ ├── Livewire/
│ │ ├── TaskManager.php
│ │ ├── TaskForm.php
│ │ ├── TaskList.php
│ │ ├── TaskFilter.php
│ │ └── TaskStats.php
│ ├── Models/
│ │ └── Task.php
│ └── Http/
│ └── Controllers/
│ └── TaskController.php
├── resources/
│ └── views/
│ ├── livewire/
│ │ ├── task-manager.blade.php
│ │ ├── task-form.blade.php
│ │ ├── task-list.blade.php
│ │ ├── task-filter.blade.php
│ │ └── task-stats.blade.php
│ └── layouts/
│ └── app.blade.php
├── database/
│ └── migrations/
│ └── create_tasks_table.php
└── routes/
└── web.php<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Task extends Model
{
use HasFactory;
protected $fillable = [
'title',
'description',
'priority',
'due_date',
'completed',
];
protected $casts = [
'completed' => 'boolean',
'due_date' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public function scopeActive($query)
{
return $query->where('completed', false);
}
public function scopeCompleted($query)
{
return $query->where('completed', true);
}
public function scopeByPriority($query, string $priority)
{
return $query->where('priority', $priority);
}
}<?php
namespace App\Livewire;
use App\Models\Task;
use Livewire\Component;
use Livewire\Attributes\Computed;
class TaskManager extends Component
{
public string $filter = 'all';
public string $sortBy = 'recent';
public string $searchQuery = '';
#[Computed]
public function tasks()
{
$query = Task::query();
// Apply filter
match ($this->filter) {
'active' => $query->active(),
'completed' => $query->completed(),
default => null,
};
// Apply search
if ($this->searchQuery) {
$query->where('title', 'like', "%{$this->searchQuery}%")
->orWhere('description', 'like', "%{$this->searchQuery}%");
}
// Apply sorting
match ($this->sortBy) {
'priority' => $query->orderByRaw("FIELD(priority, 'high', 'medium', 'low')"),
'due_date' => $query->orderBy('due_date'),
default => $query->latest(),
};
return $query->get();
}
#[Computed]
public function stats()
{
return [
'total' => Task::count(),
'active' => Task::active()->count(),
'completed' => Task::completed()->count(),
];
}
public function setFilter(string $filter): void
{
$this->filter = $filter;
}
public function setSortBy(string $sortBy): void
{
$this->sortBy = $sortBy;
}
public function render()
{
return view('livewire.task-manager');
}
}<?php
namespace App\Livewire;
use App\Models\Task;
use Livewire\Component;
class TaskForm extends Component
{
public string $title = '';
public string $description = '';
public string $priority = 'medium';
public string $dueDate = '';
protected array $rules = [
'title' => 'required|min:3|max:100',
'description' => 'nullable|max:500',
'priority' => 'required|in:low,medium,high',
'dueDate' => 'nullable|date|after:today',
];
public function submit(): void
{
$this->validate();
Task::create([
'title' => $this->title,
'description' => $this->description,
'priority' => $this->priority,
'due_date' => $this->dueDate ?: null,
'completed' => false,
]);
$this->reset();
$this->dispatch('task-created');
session()->flash('success', 'Task created successfully!');
}
public function render()
{
return view('livewire.task-form');
}
}<?php
namespace App\Livewire;
use App\Models\Task;
use Livewire\Component;
class TaskList extends Component
{
public array $tasks = [];
protected $listeners = [
'task-created' => 'refreshTasks',
];
public function mount(): void
{
$this->refreshTasks();
}
public function refreshTasks(): void
{
$this->tasks = Task::latest()->get()->toArray();
}
public function toggleTask(int $id): void
{
$task = Task::find($id);
if ($task) {
$task->update(['completed' => !$task->completed]);
$this->refreshTasks();
}
}
public function deleteTask(int $id): void
{
Task::find($id)?->delete();
$this->refreshTasks();
}
public function render()
{
return view('livewire.task-list', [
'tasks' => $this->tasks,
]);
}
}<?php
namespace App\Livewire;
use Livewire\Component;
class TaskFilter extends Component
{
public string $activeFilter = 'all';
public string $activeSortBy = 'recent';
public function setFilter(string $filter): void
{
$this->activeFilter = $filter;
$this->dispatch('filter-changed', filter: $filter);
}
public function setSortBy(string $sortBy): void
{
$this->activeSortBy = $sortBy;
$this->dispatch('sort-changed', sortBy: $sortBy);
}
public function render()
{
return view('livewire.task-filter');
}
}<?php
namespace App\Livewire;
use App\Models\Task;
use Livewire\Component;
use Livewire\Attributes\Computed;
class TaskStats extends Component
{
protected $listeners = [
'task-created' => '$refresh',
];
#[Computed]
public function stats()
{
return [
'total' => Task::count(),
'active' => Task::active()->count(),
'completed' => Task::completed()->count(),
'completionRate' => Task::count() > 0
? round((Task::completed()->count() / Task::count()) * 100)
: 0,
];
}
public function render()
{
return view('livewire.task-stats');
}
}Blade views untuk Livewire sama dengan English version, hanya dengan Indonesian labels dan comments. Struktur dan styling tetap sama.
// ✅ Keep components focused dan single-responsibility
// ✅ Use computed properties untuk expensive calculations
// ✅ Leverage lifecycle hooks appropriately
// ✅ Use event dispatching untuk inter-component communication
// ❌ Avoid large monolithic components
// ❌ Don't perform heavy operations dalam render()
// ❌ Avoid tight coupling antara components// ✅ Use wire:model untuk two-way binding
// ✅ Use debounce/throttle untuk performance
// ✅ Use lazy untuk blur events
// ✅ Validate on update ketika appropriate
// ❌ Avoid binding ke large arrays
// ❌ Don't use wire:model pada computed properties
// ❌ Avoid unnecessary re-renders// ✅ Define rules sebagai class properties
// ✅ Use custom messages untuk better UX
// ✅ Validate on submit dan on update
// ✅ Use validateOnly untuk specific fields
// ❌ Avoid validating everything pada every keystroke
// ❌ Don't ignore validation errors
// ❌ Avoid complex validation logic dalam components// ✅ Use computed properties untuk caching
// ✅ Use pagination untuk large datasets
// ✅ Lazy load components dengan wire:lazy
// ✅ Use wire:loading untuk show loading states
// ❌ Avoid loading semua data at once
// ❌ Don't perform N+1 queries
// ❌ Avoid unnecessary component re-renders// ✅ Always validate user input
// ✅ Use authorization checks
// ✅ Sanitize output dalam views
// ✅ Use CSRF protection (automatic dengan Livewire)
// ❌ Don't trust user input
// ❌ Avoid exposing sensitive data
// ❌ Don't skip authorization checks// ❌ Wrong - can't bind ke computed properties
public function getTotal()
{
return $this->items->sum('price');
}
// ✅ Correct - use regular properties atau computed attributes
#[Computed]
public function total()
{
return $this->items->sum('price');
}// ❌ Wrong - form data persists
public function submit()
{
$this->validate();
Task::create($this->toArray());
// Form data masih visible
}
// ✅ Correct - reset setelah submission
public function submit()
{
$this->validate();
Task::create($this->toArray());
$this->reset();
}// ❌ Wrong - components don't communicate
class ComponentA extends Component {}
class ComponentB extends Component {}
// ✅ Correct - use event dispatching
class ComponentA extends Component
{
public function notify()
{
$this->dispatch('event-name', data: $value);
}
}
class ComponentB extends Component
{
protected $listeners = ['event-name' => 'handleEvent'];
public function handleEvent($data) {}
}// ❌ Wrong - N+1 queries
public function render()
{
return view('livewire.tasks', [
'tasks' => Task::all(), // Queries users untuk each task
]);
}
// ✅ Correct - eager load relationships
public function render()
{
return view('livewire.tasks', [
'tasks' => Task::with('user')->get(),
]);
}<!-- ❌ Wrong - no feedback during loading -->
<button wire:click="submit">Submit</button>
<!-- ✅ Correct - show loading state -->
<button wire:click="submit" wire:loading.attr="disabled">
<span wire:loading.remove>Submit</span>
<span wire:loading>Loading...</span>
</button>Livewire revolutionize Laravel development dengan enable developers untuk build interactive, reactive user interfaces entirely dalam PHP. Dengan eliminate kebutuhan untuk manual JavaScript dan AJAX, Livewire dramatically improve developer experience dan productivity.
Task management application yang kita build demonstrate semua core Livewire concepts dalam action. Understanding components, data binding, events, lifecycle hooks, dan validation adalah essential untuk building modern Livewire applications.
Key takeaways:
Next steps:
Livewire adalah future of Laravel development. Keep learning, building, dan pushing boundaries dari apa yang possible dengan full-stack PHP.