Livewire Introduction
Introduction to Laravel Livewire
Laravel Livewire is a full-stack framework for Laravel that makes building dynamic interfaces simple, without leaving the comfort of Laravel. It allows you to build reactive, dynamic interfaces using server-side rendering without writing JavaScript. Livewire components are PHP classes that render Blade templates.
Key Benefits of Livewire:
- Build dynamic interfaces without writing JavaScript
- No API required - direct communication with backend
- Automatic form validation and error handling
- Real-time updates without page refresh
- Server-side rendering for better SEO
- Integration with Laravel ecosystem (validation, authorization, events)
Installing Livewire
To get started with Livewire in your Laravel application, install it via Composer:
# Install Livewire
composer require livewire/livewire
# Publish Livewire configuration (optional)
php artisan livewire:publish --config
Once installed, include Livewire's JavaScript and CSS assets in your main layout file:
<!DOCTYPE html>
<html>
<head>
<title>My Livewire App</title>
@livewireStyles
</head>
<body>
{{ $slot }}
@livewireScripts
</body>
</html>
Pro Tip: Place @livewireStyles before your closing </head> tag and @livewireScripts before your closing </body> tag for optimal performance.
Creating Your First Livewire Component
Livewire components consist of a PHP class and a Blade view. Use the artisan command to generate both:
# Create a Livewire component
php artisan make:livewire Counter
# This creates:
# app/Livewire/Counter.php
# resources/views/livewire/counter.blade.php
# You can also create in subdirectories
php artisan make:livewire Blog/PostList
Here's a simple counter component example:
<?php
// app/Livewire/Counter.php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
// Public property automatically available in view
public $count = 0;
// Method called from view
public function increment()
{
$this->count++;
}
public function decrement()
{
$this->count--;
}
public function render()
{
return view('livewire.counter');
}
}
<!-- resources/views/livewire/counter.blade.php -->
<div>
<h1>Count: {{ $count }}</h1>
<button wire:click="increment">+</button>
<button wire:click="decrement">-</button>
</div>
To use the component in any Blade view:
<!-- Using tag syntax -->
<livewire:counter />
<!-- Using Blade directive -->
@livewire('counter')
<!-- Passing parameters -->
<livewire:counter :initial-count="10" />
Properties and Data Binding
Livewire provides powerful two-way data binding using the wire:model directive. Public properties in your component class are automatically synced with the view.
<?php
// app/Livewire/SearchUsers.php
namespace App\Livewire;
use Livewire\Component;
use App\Models\User;
class SearchUsers extends Component
{
public $search = '';
public $results = [];
// This method is called whenever $search changes
public function updatedSearch()
{
$this->results = User::where('name', 'like', '%'.$this->search.'%')
->limit(10)
->get();
}
public function render()
{
return view('livewire.search-users');
}
}
<!-- resources/views/livewire/search-users.blade.php -->
<div>
<input type="text" wire:model.live="search" placeholder="Search users...">
<ul>
@foreach($results as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
</div>
Data Binding Modifiers:
wire:model.live- Updates on every input event (real-time)wire:model.blur- Updates when input loses focuswire:model.debounce.500ms- Updates after 500ms of no typingwire:model.lazy- Updates on "change" event
Actions and Methods
Livewire actions allow you to call methods on your component from the frontend using the wire:click, wire:submit, and other event directives.
<?php
// app/Livewire/TodoList.php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Todo;
class TodoList extends Component
{
public $newTodo = '';
public $todos = [];
public function mount()
{
$this->todos = Todo::where('user_id', auth()->id())
->latest()
->get();
}
public function addTodo()
{
$this->validate([
'newTodo' => 'required|min:3|max:255'
]);
Todo::create([
'user_id' => auth()->id(),
'title' => $this->newTodo,
'completed' => false,
]);
$this->newTodo = '';
$this->mount(); // Refresh list
session()->flash('message', 'Todo added successfully!');
}
public function toggleComplete($todoId)
{
$todo = Todo::find($todoId);
$todo->update(['completed' => !$todo->completed]);
$this->mount();
}
public function deleteTodo($todoId)
{
Todo::find($todoId)->delete();
$this->mount();
}
public function render()
{
return view('livewire.todo-list');
}
}
<!-- resources/views/livewire/todo-list.blade.php -->
<div>
@if (session()->has('message'))
<div class="alert alert-success">{{ session('message') }}</div>
@endif
<form wire:submit.prevent="addTodo">
<input type="text" wire:model="newTodo" placeholder="Add new todo">
@error('newTodo') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Add</button>
</form>
<ul>
@foreach($todos as $todo)
<li>
<input type="checkbox"
wire:click="toggleComplete({{ $todo->id }})"
@if($todo->completed) checked @endif>
<span class="{{ $todo->completed ? 'line-through' : '' }}">
{{ $todo->title }}
</span>
<button wire:click="deleteTodo({{ $todo->id }})">Delete</button>
</li>
@endforeach
</ul>
</div>
Lifecycle Hooks
Livewire components have several lifecycle hooks that allow you to execute code at specific points in the component's lifecycle.
Available Lifecycle Hooks:
mount()- Called when component is initialized (like constructor)hydrate()- Called on subsequent requests before any actionupdating($name, $value)- Called before a property is updatedupdated($name, $value)- Called after a property is updatedupdatingPropertyName()- Called before specific property updatesupdatedPropertyName()- Called after specific property updates
<?php
namespace App\Livewire;
use Livewire\Component;
class ProductForm extends Component
{
public $name = '';
public $price = 0;
public $category = '';
// Called when component is initialized
public function mount($productId = null)
{
if ($productId) {
$product = Product::find($productId);
$this->name = $product->name;
$this->price = $product->price;
$this->category = $product->category;
}
}
// Called on every request (after mount on first load)
public function hydrate()
{
// Reset validation errors
}
// Called before any property is updated
public function updating($name, $value)
{
// Sanitize input
if ($name === 'name') {
return ucfirst($value);
}
}
// Called after any property is updated
public function updated($name, $value)
{
$this->validateOnly($name, [
'name' => 'required|min:3',
'price' => 'required|numeric|min:0',
'category' => 'required'
]);
}
// Called before $price is updated
public function updatingPrice($value)
{
// Ensure price is positive
return max(0, $value);
}
// Called after $price is updated
public function updatedPrice($value)
{
// Automatically calculate discounted price
$this->discountedPrice = $value * 0.9;
}
public function render()
{
return view('livewire.product-form');
}
}
Best Practice: Use mount() for one-time initialization and hydrate() for logic that needs to run on every request. Use property-specific hooks like updatedPrice() for targeted validation and transformation.
Loading States
Livewire provides built-in loading state indicators using the wire:loading directive:
<div>
<button wire:click="save">
Save
<span wire:loading wire:target="save">Saving...</span>
</button>
<!-- Show spinner during any action -->
<div wire:loading>
Processing...
</div>
<!-- Show spinner for specific action -->
<div wire:loading wire:target="save">
Saving your changes...
</div>
<!-- Remove element during loading -->
<div wire:loading.remove>
Content hidden during loading
</div>
<!-- Loading states for properties -->
<input wire:model="search">
<div wire:loading wire:target="search">Searching...</div>
</div>
Common Pitfall: Don't forget to include both @livewireStyles and @livewireScripts in your layout. Missing these will cause Livewire components to not function properly.
Practice Exercise 1: Contact Form Component
Create a Livewire component for a contact form with the following requirements:
- Fields: name, email, subject, message
- Real-time validation as user types (use wire:model.blur)
- Display validation errors below each field
- Show loading state when submitting
- Display success message after submission
- Clear form after successful submission
Hint: Use $this->validate() in your submit method and wire:loading for the loading state.
Practice Exercise 2: Live Search Component
Build a live search component that:
- Searches products by name as user types
- Uses debouncing (500ms) to avoid too many requests
- Displays "No results found" when search returns empty
- Shows loading indicator while searching
- Allows clicking on a result to view details
- Implements a method to clear search results
Bonus: Add a minimum character count (3) before performing search.
Practice Exercise 3: Dynamic Calculator
Create a simple calculator component:
- Two number inputs (wire:model.live)
- Dropdown to select operation (+, -, *, /)
- Display result automatically when inputs change
- Handle division by zero gracefully
- Show calculation history (last 5 calculations)
- Button to clear history
Challenge: Use lifecycle hooks to validate inputs and format the result to 2 decimal places.
Summary
In this lesson, you learned about Laravel Livewire, a powerful framework for building dynamic interfaces without writing JavaScript. Key takeaways include:
- Installing and setting up Livewire in Laravel applications
- Creating Livewire components with PHP classes and Blade views
- Using public properties and two-way data binding with wire:model
- Implementing actions and methods with wire:click and other event directives
- Leveraging lifecycle hooks for initialization and property updates
- Adding loading states for better user experience
- Real-time form validation and error handling
Livewire bridges the gap between traditional server-side rendering and modern reactive interfaces, allowing you to build dynamic applications while staying in the Laravel ecosystem. In the next lesson, we'll explore building real-time features with broadcasting and WebSockets.