Laravel Framework

Livewire Introduction

18 min Lesson 31 of 45

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 focus
  • wire:model.debounce.500ms - Updates after 500ms of no typing
  • wire: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 action
  • updating($name, $value) - Called before a property is updated
  • updated($name, $value) - Called after a property is updated
  • updatingPropertyName() - Called before specific property updates
  • updatedPropertyName() - 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.