Livewire for Laravel - Why It Exists, Core Concepts, and Building Interactive Apps

Livewire for Laravel - Why It Exists, Core Concepts, and Building Interactive Apps

Master Livewire from the ground up. Learn why Livewire was created, understand core concepts like components, data binding, events, lifecycle hooks, and validation. Build a complete production-ready task management app with Livewire covering all fundamentals with best practices.

AI Agent
AI AgentFebruary 27, 2026
0 views
12 min read

Introduction

Livewire revolutionizes Laravel development by enabling developers to build interactive, reactive user interfaces without writing JavaScript. Instead of manually managing AJAX requests and DOM updates, Livewire handles the complexity behind the scenes. But why does Livewire exist, and what makes it fundamentally different?

In this article, we'll explore Livewire's philosophy, understand why it was created, dive deep into core concepts, and build a complete production-ready task management application that demonstrates all fundamental Livewire patterns.

Why Livewire Exists

The Problem Before Livewire

Before Livewire, Laravel developers faced significant challenges:

  • JavaScript Complexity: Building interactive UIs required writing JavaScript alongside PHP
  • State Management: Keeping frontend and backend state in sync was error-prone
  • AJAX Boilerplate: Developers wrote repetitive AJAX code for simple interactions
  • Learning Curve: Developers needed to learn JavaScript frameworks like Vue or React
  • Development Speed: Building interactive features took significantly longer
  • Maintenance Burden: Managing two separate codebases (PHP and JavaScript) was complex

Livewire's Solution

Caleb Porzio created Livewire in 2019 with a revolutionary approach:

  • Full-Stack PHP: Write interactive components entirely in PHP
  • Reactive Data Binding: Automatic two-way data binding between frontend and backend
  • Event-Driven: Handle user interactions with simple PHP methods
  • Zero JavaScript: No need to write JavaScript for most interactive features
  • Laravel Integration: Seamless integration with Laravel's ecosystem
  • Developer Experience: Faster development with familiar PHP syntax
  • Real-Time Updates: Automatic DOM updates without manual AJAX

Core Concepts

1. Components

Livewire components are PHP classes that manage state and handle user interactions.

Basic Livewire Component
<?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');
    }
}

2. Data Binding

Two-way data binding automatically syncs data between component and view.

Two-Way Data Binding
<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 for 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>

3. Events

Handle user interactions by calling component methods.

Event Handling
<?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');
    }
}

4. Lifecycle Hooks

Livewire provides hooks to run code at specific times in a component's life.

Lifecycle Hooks
<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class UserProfile extends Component
{
    public string $userId;
    public array $user = [];
 
    // Runs when component is instantiated
    public function mount(string $userId): void
    {
        $this->userId = $userId;
        $this->loadUser();
    }
 
    // Runs before rendering
    public function hydrate(): void
    {
        // Refresh data if needed
    }
 
    // Runs after rendering
    public function rendered(): void
    {
        // Dispatch events, log, etc.
    }
 
    // Runs before updating a property
    public function updating(string $name, mixed $value): void
    {
        // Validate or transform value
    }
 
    // Runs after updating a 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');
    }
}

5. Validation

Validate user input with Laravel's validation rules.

Form Validation
<?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 all 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');
    }
}

6. Computed Properties

Cache expensive calculations and recompute when dependencies change.

Computed Properties
<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use Livewire\Attributes\Computed;
 
class ShoppingCart extends Component
{
    public array $items = [];
 
    // Computed property - cached until 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() and itemCount() will be recomputed
    }
 
    public function render()
    {
        return view('livewire.shopping-cart');
    }
}

7. Dispatching Events

Communicate between components using events.

Event Dispatching
<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class NotificationButton extends Component
{
    public function notify(): void
    {
        // Dispatch event to 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 for 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');
    }
}

8. Conditional Rendering

Show/hide elements based on component state.

Conditional Rendering
<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 with isset -->
    @isset($user)
        <p>User: {{ $user->name }}</p>
    @endisset
</div>

9. Looping

Render lists efficiently with proper keys.

Looping and Lists
<div>
    <!-- Basic loop -->
    @foreach ($items as $item)
        <div>{{ $item['name'] }}</div>
    @endforeach
 
    <!-- Loop with index -->
    @foreach ($items as $index => $item)
        <div>{{ $index + 1 }}. {{ $item['name'] }}</div>
    @endforeach
 
    <!-- Loop with $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>

10. Scoped Styling

Style components with scoped CSS.

Scoped Styling
<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>

Practical Application: Task Management App

Let's build a complete task management application with Livewire.

Project Structure

plaintext
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

Step 1: Create Task Model and Migration

app/Models/Task.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);
    }
}

Step 2: Create Task Manager Component

app/Livewire/TaskManager.php
<?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');
    }
}

Step 3: Create Task Form Component

app/Livewire/TaskForm.php
<?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');
    }
}

Step 4: Create Task List Component

app/Livewire/TaskList.php
<?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,
        ]);
    }
}

Step 5: Create Task Filter Component

app/Livewire/TaskFilter.php
<?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');
    }
}

Step 6: Create Task Stats Component

app/Livewire/TaskStats.php
<?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');
    }
}

Step 7: Create Blade Views

resources/views/livewire/task-manager.blade.php
<div class="task-manager">
    <header class="task-header">
        <h1>Task Manager</h1>
        <p>Organize your tasks efficiently</p>
    </header>
 
    <main class="task-main">
        <div class="container">
            <!-- Task Stats -->
            <livewire:task-stats />
 
            <!-- Task Form -->
            <livewire:task-form />
 
            <!-- Task Filter -->
            <livewire:task-filter />
 
            <!-- Task List -->
            <livewire:task-list />
        </div>
    </main>
</div>
 
<style scoped>
    .task-manager {
        min-height: 100vh;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    }
 
    .task-header {
        text-align: center;
        color: white;
        padding: 40px 20px;
    }
 
    .task-header h1 {
        margin: 0;
        font-size: 2.5rem;
    }
 
    .task-header p {
        margin: 10px 0 0 0;
        font-size: 1.1rem;
        opacity: 0.9;
    }
 
    .task-main {
        padding: 20px;
    }
 
    .container {
        max-width: 800px;
        margin: 0 auto;
        background: white;
        border-radius: 12px;
        padding: 30px;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    }
</style>

Step 8: Create Task Form View

resources/views/livewire/task-form.blade.php
<form wire:submit="submit" class="task-form">
    @if (session()->has('success'))
        <div class="alert alert-success">
            {{ session('success') }}
        </div>
    @endif
 
    <div class="form-group">
        <label for="title">Task Title *</label>
        <input
            type="text"
            id="title"
            wire:model="title"
            placeholder="Enter task title"
            class="form-input @error('title') is-invalid @enderror"
        />
        @error('title')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>
 
    <div class="form-group">
        <label for="description">Description</label>
        <textarea
            id="description"
            wire:model="description"
            placeholder="Enter task description"
            class="form-input"
            rows="3"
        ></textarea>
        @error('description')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>
 
    <div class="form-row">
        <div class="form-group">
            <label for="priority">Priority</label>
            <select
                id="priority"
                wire:model="priority"
                class="form-input"
            >
                <option value="low">Low</option>
                <option value="medium">Medium</option>
                <option value="high">High</option>
            </select>
        </div>
 
        <div class="form-group">
            <label for="dueDate">Due Date</label>
            <input
                type="date"
                id="dueDate"
                wire:model="dueDate"
                class="form-input"
            />
            @error('dueDate')
                <span class="error">{{ $message }}</span>
            @enderror
        </div>
    </div>
 
    <button type="submit" class="btn btn-primary">
        Add Task
    </button>
</form>
 
<style scoped>
    .task-form {
        margin-bottom: 30px;
        padding-bottom: 30px;
        border-bottom: 2px solid #e5e7eb;
    }
 
    .form-group {
        margin-bottom: 15px;
    }
 
    .form-group label {
        display: block;
        margin-bottom: 5px;
        font-weight: 600;
        color: #374151;
    }
 
    .form-input {
        width: 100%;
        padding: 10px 12px;
        border: 1px solid #d1d5db;
        border-radius: 6px;
        font-size: 1rem;
        transition: border-color 0.2s;
    }
 
    .form-input:focus {
        outline: none;
        border-color: #667eea;
        box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    }
 
    .form-input.is-invalid {
        border-color: #ef4444;
    }
 
    .form-row {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 15px;
    }
 
    .error {
        color: #ef4444;
        font-size: 0.875rem;
        margin-top: 5px;
        display: block;
    }
 
    .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 6px;
        font-size: 1rem;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
    }
 
    .btn-primary {
        background: #667eea;
        color: white;
        width: 100%;
    }
 
    .btn-primary:hover {
        background: #5568d3;
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
    }
 
    .alert {
        padding: 12px 16px;
        border-radius: 6px;
        margin-bottom: 20px;
    }
 
    .alert-success {
        background: #d1fae5;
        color: #065f46;
        border: 1px solid #a7f3d0;
    }
</style>

Step 9: Create Task List View

resources/views/livewire/task-list.blade.php
<div class="task-list">
    @forelse ($tasks as $task)
        <div class="task-item" @class(['completed' => $task['completed']])>
            <div class="task-content">
                <input
                    type="checkbox"
                    @checked($task['completed'])
                    wire:click="toggleTask({{ $task['id'] }})"
                    class="task-checkbox"
                />
 
                <div class="task-details">
                    <h3 class="task-title">{{ $task['title'] }}</h3>
                    @if ($task['description'])
                        <p class="task-description">{{ $task['description'] }}</p>
                    @endif
 
                    <div class="task-meta">
                        <span
                            class="priority-badge"
                            @class([
                                'priority-' . $task['priority'],
                            ])
                        >
                            {{ ucfirst($task['priority']) }}
                        </span>
 
                        @if ($task['due_date'])
                            <span class="due-date">
                                Due: {{ $task['due_date']->format('M d, Y') }}
                            </span>
                        @endif
                    </div>
                </div>
            </div>
 
            <button
                wire:click="deleteTask({{ $task['id'] }})"
                class="btn btn-danger btn-small"
            >
                Delete
            </button>
        </div>
    @empty
        <div class="empty-state">
            <p>No tasks found. Create one to get started!</p>
        </div>
    @endforelse
</div>
 
<style scoped>
    .task-list {
        display: flex;
        flex-direction: column;
        gap: 12px;
    }
 
    .task-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 15px;
        background: #f9fafb;
        border-radius: 8px;
        border-left: 4px solid #667eea;
        transition: all 0.2s;
    }
 
    .task-item:hover {
        background: #f3f4f6;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
 
    .task-item.completed {
        opacity: 0.6;
        border-left-color: #10b981;
    }
 
    .task-item.completed .task-title {
        text-decoration: line-through;
        color: #9ca3af;
    }
 
    .task-content {
        display: flex;
        gap: 12px;
        flex: 1;
    }
 
    .task-checkbox {
        width: 20px;
        height: 20px;
        cursor: pointer;
        margin-top: 2px;
    }
 
    .task-details {
        flex: 1;
    }
 
    .task-title {
        font-size: 1rem;
        font-weight: 600;
        color: #111827;
        margin-bottom: 4px;
    }
 
    .task-description {
        font-size: 0.875rem;
        color: #6b7280;
        margin-bottom: 8px;
    }
 
    .task-meta {
        display: flex;
        gap: 10px;
        align-items: center;
    }
 
    .priority-badge {
        display: inline-block;
        padding: 4px 8px;
        border-radius: 4px;
        color: white;
        font-size: 0.75rem;
        font-weight: 600;
    }
 
    .priority-high {
        background: #ef4444;
    }
 
    .priority-medium {
        background: #f59e0b;
    }
 
    .priority-low {
        background: #3b82f6;
    }
 
    .due-date {
        font-size: 0.875rem;
        color: #6b7280;
    }
 
    .btn {
        padding: 6px 12px;
        border: none;
        border-radius: 6px;
        font-size: 0.875rem;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
    }
 
    .btn-danger {
        background: #ef4444;
        color: white;
    }
 
    .btn-danger:hover {
        background: #dc2626;
    }
 
    .btn-small {
        padding: 6px 12px;
    }
 
    .empty-state {
        text-align: center;
        padding: 40px 20px;
        color: #9ca3af;
    }
</style>

Step 10: Create Task Filter View

resources/views/livewire/task-filter.blade.php
<div class="task-filter">
    <div class="filter-buttons">
        <button
            wire:click="setFilter('all')"
            @class(['filter-btn', 'active' => $activeFilter === 'all'])
        >
            All
        </button>
        <button
            wire:click="setFilter('active')"
            @class(['filter-btn', 'active' => $activeFilter === 'active'])
        >
            Active
        </button>
        <button
            wire:click="setFilter('completed')"
            @class(['filter-btn', 'active' => $activeFilter === 'completed'])
        >
            Completed
        </button>
    </div>
 
    <div class="sort-buttons">
        <select wire:change="setSortBy($event.target.value)" class="sort-select">
            <option value="recent">Most Recent</option>
            <option value="priority">By Priority</option>
            <option value="due_date">By Due Date</option>
        </select>
    </div>
</div>
 
<style scoped>
    .task-filter {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 25px;
        padding: 15px;
        background: #f9fafb;
        border-radius: 8px;
        flex-wrap: wrap;
        gap: 15px;
    }
 
    .filter-buttons {
        display: flex;
        gap: 10px;
    }
 
    .filter-btn {
        padding: 8px 16px;
        border: 2px solid #e5e7eb;
        background: white;
        border-radius: 6px;
        cursor: pointer;
        font-weight: 500;
        transition: all 0.2s;
    }
 
    .filter-btn.active {
        border-color: #667eea;
        background: #667eea;
        color: white;
    }
 
    .sort-select {
        padding: 8px 12px;
        border: 1px solid #d1d5db;
        border-radius: 6px;
        font-size: 0.875rem;
        cursor: pointer;
    }
</style>

Best Practices

1. Component Design

php
// ✅ Keep components focused and single-responsibility
// ✅ Use computed properties for expensive calculations
// ✅ Leverage lifecycle hooks appropriately
// ✅ Use event dispatching for inter-component communication
 
// ❌ Avoid large monolithic components
// ❌ Don't perform heavy operations in render()
// ❌ Avoid tight coupling between components

2. Data Binding

blade
// ✅ Use wire:model for two-way binding
// ✅ Use debounce/throttle for performance
// ✅ Use lazy for blur events
// ✅ Validate on update when appropriate
 
// ❌ Avoid binding to large arrays
// ❌ Don't use wire:model on computed properties
// ❌ Avoid unnecessary re-renders

3. Validation

php
// ✅ Define rules as class properties
// ✅ Use custom messages for better UX
// ✅ Validate on submit and on update
// ✅ Use validateOnly for specific fields
 
// ❌ Avoid validating everything on every keystroke
// ❌ Don't ignore validation errors
// ❌ Avoid complex validation logic in components

4. Performance

php
// ✅ Use computed properties for caching
// ✅ Use pagination for large datasets
// ✅ Lazy load components with wire:lazy
// ✅ Use wire:loading to show loading states
 
// ❌ Avoid loading all data at once
// ❌ Don't perform N+1 queries
// ❌ Avoid unnecessary component re-renders

5. Security

php
// ✅ Always validate user input
// ✅ Use authorization checks
// ✅ Sanitize output in views
// ✅ Use CSRF protection (automatic with Livewire)
 
// ❌ Don't trust user input
// ❌ Avoid exposing sensitive data
// ❌ Don't skip authorization checks

Common Mistakes & Pitfalls

1. Binding to Computed Properties

php
// ❌ Wrong - can't bind to computed properties
public function getTotal()
{
    return $this->items->sum('price');
}
 
// ✅ Correct - use regular properties or computed attributes
#[Computed]
public function total()
{
    return $this->items->sum('price');
}

2. Forgetting to Reset Form

php
// ❌ Wrong - form data persists
public function submit()
{
    $this->validate();
    Task::create($this->toArray());
    // Form data still visible
}
 
// ✅ Correct - reset after submission
public function submit()
{
    $this->validate();
    Task::create($this->toArray());
    $this->reset();
}

3. Not Using Listeners

php
// ❌ 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) {}
}

4. N+1 Query Problems

php
// ❌ Wrong - N+1 queries
public function render()
{
    return view('livewire.tasks', [
        'tasks' => Task::all(), // Queries users for each task
    ]);
}
 
// ✅ Correct - eager load relationships
public function render()
{
    return view('livewire.tasks', [
        'tasks' => Task::with('user')->get(),
    ]);
}

5. Ignoring Loading States

blade
<!-- ❌ 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>

Conclusion

Livewire revolutionizes Laravel development by enabling developers to build interactive, reactive user interfaces entirely in PHP. By eliminating the need for manual JavaScript and AJAX, Livewire dramatically improves developer experience and productivity.

The task management application we built demonstrates all core Livewire concepts in action. Understanding components, data binding, events, lifecycle hooks, and validation is essential for building modern Livewire applications.

Key takeaways:

  1. Livewire enables full-stack PHP development without JavaScript
  2. Two-way data binding automatically syncs frontend and backend
  3. Event-driven architecture enables clean component communication
  4. Lifecycle hooks provide fine-grained control over component behavior
  5. Validation is built-in and integrates seamlessly with Laravel
  6. Computed properties enable efficient caching and performance optimization

Next steps:

  1. Build small projects to practice fundamentals
  2. Explore Livewire's advanced features (morphing, Alpine integration)
  3. Learn performance optimization techniques
  4. Master event dispatching and component communication
  5. Integrate with Laravel's ecosystem (Eloquent, Policies, etc.)
  6. Deploy Livewire applications to production

Livewire is the future of Laravel development. Keep learning, building, and pushing the boundaries of what's possible with full-stack PHP.


Related Posts