JavaScript frameworks complicate simple interactions. Laravel Livewire builds dynamic interfaces using PHP and Blade. At ZIRA Software, Livewire reduces frontend complexity while maintaining rich interactivity.
Why Livewire?
Traditional approach:
- Separate frontend framework (Vue/React)
- API endpoints for data
- State management complexity
- Build process required
Livewire approach:
- Write PHP, not JavaScript
- Blade templates
- Automatic state synchronization
- No build step
Installation
composer require livewire/livewire
# Publish config (optional)
php artisan livewire:publish --config
Add to layout:
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
@livewireStyles
</head>
<body>
{{ $slot }}
@livewireScripts
</body>
</html>
Creating Components
Generate component:
php artisan make:livewire Counter
# Creates:
# app/Http/Livewire/Counter.php
# resources/views/livewire/counter.blade.php
Component class:
// app/Http/Livewire/Counter.php
namespace App\Http\Livewire;
use Livewire\Component;
class Counter extends Component
{
public $count = 0;
public function increment()
{
$this->count++;
}
public function decrement()
{
$this->count--;
}
public function render()
{
return view('livewire.counter');
}
}
Component view:
<!-- resources/views/livewire/counter.blade.php -->
<div class="p-6">
<h1 class="text-2xl mb-4">Count: {{ $count }}</h1>
<div class="space-x-2">
<button wire:click="increment" class="btn btn-primary">
+
</button>
<button wire:click="decrement" class="btn btn-secondary">
-
</button>
</div>
</div>
Use in blade:
<!-- resources/views/welcome.blade.php -->
<div>
@livewire('counter')
</div>
Data Binding
Two-way binding:
// Component
class SearchPosts extends Component
{
public $search = '';
public function render()
{
return view('livewire.search-posts', [
'posts' => Post::where('title', 'like', "%{$this->search}%")->get(),
]);
}
}
<!-- View -->
<div>
<input
type="text"
wire:model="search"
placeholder="Search posts..."
class="form-input"
>
@foreach($posts as $post)
<div class="post">{{ $post->title }}</div>
@endforeach
</div>
Debouncing:
<!-- Update after 500ms of no typing -->
<input wire:model.debounce.500m="search" type="text">
<!-- Update only on blur -->
<input wire:model.lazy="email" type="email">
Real-Time Validation
// Component
class ContactForm extends Component
{
public $name;
public $email;
public $message;
protected $rules = [
'name' => 'required|min:3',
'email' => 'required|email',
'message' => 'required|min:10',
];
public function updated($propertyName)
{
$this->validateOnly($propertyName);
}
public function submit()
{
$validated = $this->validate();
// Process form
Contact::create($validated);
session()->flash('message', 'Message sent successfully!');
$this->reset();
}
public function render()
{
return view('livewire.contact-form');
}
}
<!-- View -->
<form wire:submit.prevent="submit">
@if (session()->has('message'))
<div class="alert alert-success">
{{ session('message') }}
</div>
@endif
<div class="mb-4">
<label>Name</label>
<input wire:model="name" type="text" class="form-input">
@error('name') <span class="error">{{ $message }}</span> @enderror
</div>
<div class="mb-4">
<label>Email</label>
<input wire:model="email" type="email" class="form-input">
@error('email') <span class="error">{{ $message }}</span> @enderror
</div>
<div class="mb-4">
<label>Message</label>
<textarea wire:model="message" class="form-input"></textarea>
@error('message') <span class="error">{{ $message }}</span> @enderror
</div>
<button type="submit" class="btn btn-primary">Send</button>
</form>
File Uploads
use Livewire\WithFileUploads;
class UploadPhoto extends Component
{
use WithFileUploads;
public $photo;
public function save()
{
$this->validate([
'photo' => 'image|max:1024', // 1MB Max
]);
$this->photo->store('photos');
session()->flash('message', 'Photo uploaded!');
}
public function render()
{
return view('livewire.upload-photo');
}
}
<form wire:submit.prevent="save">
<input type="file" wire:model="photo">
@error('photo') <span class="error">{{ $message }}</span> @enderror
@if ($photo)
<img src="{{ $photo->temporaryUrl() }}" class="mt-4">
@endif
<button type="submit">Save Photo</button>
</form>
Pagination
use Livewire\WithPagination;
class ShowPosts extends Component
{
use WithPagination;
public $search = '';
public function updatingSearch()
{
$this->resetPage();
}
public function render()
{
return view('livewire.show-posts', [
'posts' => Post::where('title', 'like', "%{$this->search}%")
->paginate(10),
]);
}
}
<div>
<input wire:model="search" type="text" placeholder="Search...">
@foreach($posts as $post)
<div class="post">{{ $post->title }}</div>
@endforeach
{{ $posts->links() }}
</div>
Loading States
<!-- Simple loading indicator -->
<button wire:click="save">
Save
<span wire:loading>Saving...</span>
</button>
<!-- Target specific actions -->
<button wire:click="save">
<span wire:loading.remove wire:target="save">Save</span>
<span wire:loading wire:target="save">Saving...</span>
</button>
<!-- Disable during loading -->
<button wire:click="save" wire:loading.attr="disabled">
Save
</button>
<!-- Hide/show elements -->
<div wire:loading.class="opacity-50">
Content becomes transparent during loading
</div>
Polling
Auto-refresh:
<!-- Poll every 2 seconds -->
<div wire:poll.2s>
Current time: {{ now() }}
</div>
<!-- Poll specific action -->
<div wire:poll.5="checkStatus">
Status: {{ $status }}
</div>
<!-- Stop polling when invisible -->
<div wire:poll.keep-alive>
<!-- Prevents session timeout -->
</div>
Events
Emit events:
// From component
$this->emit('postAdded', $post->id);
// Global event
$this->emit('alert', 'Post saved!');
Listen for events:
class ShowPosts extends Component
{
protected $listeners = ['postAdded' => 'refreshPosts'];
public function refreshPosts()
{
// Refresh component
}
public function render()
{
return view('livewire.show-posts', [
'posts' => Post::all(),
]);
}
}
Browser events:
$this->dispatchBrowserEvent('name-updated', ['newName' => $name]);
<script>
window.addEventListener('name-updated', event => {
alert('Name updated to: ' + event.detail.newName);
});
</script>
Inline Editing
class EditPost extends Component
{
public Post $post;
public $editing = false;
protected $rules = [
'post.title' => 'required',
'post.content' => 'required',
];
public function save()
{
$this->validate();
$this->post->save();
$this->editing = false;
session()->flash('message', 'Post updated!');
}
public function render()
{
return view('livewire.edit-post');
}
}
<div>
@if($editing)
<form wire:submit.prevent="save">
<input wire:model="post.title" type="text">
<textarea wire:model="post.content"></textarea>
<button type="submit">Save</button>
<button wire:click="$set('editing', false)">Cancel</button>
</form>
@else
<h2>{{ $post->title }}</h2>
<p>{{ $post->content }}</p>
<button wire:click="$set('editing', true)">Edit</button>
@endif
</div>
Nesting Components
<!-- Parent component -->
<div>
@foreach($posts as $post)
@livewire('edit-post', ['post' => $post], key($post->id))
@endforeach
</div>
Important: Always use key() when nesting in loops.
Performance Tips
1. Use wire:model.lazy:
<!-- Only updates on blur, not every keystroke -->
<input wire:model.lazy="search" type="text">
2. Defer loading:
<div wire:init="loadPosts">
@if($posts)
<!-- Show posts -->
@else
<div>Loading...</div>
@endif
</div>
3. Disable auto-hydration:
protected $queryString = ['search' => ['except' => '']];
Conclusion
Laravel Livewire simplifies dynamic UIs with PHP and Blade. No JavaScript framework needed for most interactive features. Perfect for Laravel developers wanting reactivity without complexity.
Building dynamic Laravel applications? Contact ZIRA Software for Livewire development.