Lección 43 de 45 12 min de lectura

Introducción a Livewire

Livewire te permite crear interfaces interactivas usando solo PHP y Blade. Sin escribir JavaScript, puedes tener componentes reactivos que actualizan la página en tiempo real.

¿Qué es Livewire?

Laravel Livewire es un framework full-stack para Laravel que permite construir interfaces dinámicas sin salir del ecosistema PHP. En lugar de escribir JavaScript para manejar interacciones del usuario, escribes componentes PHP que se renderizan como Blade y se actualizan automáticamente.

Livewire maneja toda la comunicación AJAX entre el navegador y el servidor de forma transparente. Cuando el usuario interactúa con un componente, Livewire envía una petición al servidor, ejecuta el código PHP, y actualiza solo la parte de la página que cambió.

¿Por qué usar Livewire?

  • PHP puro: No necesitas aprender Vue, React o Alpine para crear interfaces interactivas
  • Integración total con Laravel: Usa Eloquent, validación, autorización y todo lo que ya conoces
  • Menos código: Un componente Livewire reemplaza controlador + vista + JavaScript
  • SEO friendly: El HTML se renderiza en el servidor, ideal para buscadores
  • Curva de aprendizaje suave: Si sabes Laravel y Blade, ya sabes el 80% de Livewire

Instalación

Livewire 3 es la versión actual y se instala con un solo comando:

bash
composer require livewire/livewire

Para que Livewire funcione, necesitas un layout donde se renderizarán tus componentes. Puedes generarlo automáticamente con Artisan:

bash
php artisan livewire:layout

Este comando crea el archivo resources/views/components/layouts/app.blade.php:

blade
<!-- resources/views/components/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ $title ?? 'Mi App' }}</title>
</head>
<body>
    {{ $slot }}
</body>
</html>

Livewire 3 inyecta automáticamente los scripts y estilos que necesita. No hace falta añadir directivas manuales como en versiones anteriores.

Tu primer componente

Vamos a crear un contador simple que incrementa un número al hacer clic. Es el "Hola Mundo" de Livewire:

bash
php artisan make:livewire Counter

Este comando crea dos archivos:

  • app/Livewire/Counter.php - La clase del componente
  • resources/views/livewire/counter.blade.php - La vista

La clase del componente

php
<?php

declare(strict_types=1);

namespace App\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

Las propiedades públicas ($count) están disponibles automáticamente en la vista. Los métodos públicos (increment) pueden llamarse desde la vista con directivas Livewire.

La vista del componente

blade
<!-- resources/views/livewire/counter.blade.php -->
<div>
    <h2>{{ $count }}</h2>
    <button wire:click="increment">+1</button>
</div>

wire:click="increment" es la magia de Livewire. Cuando el usuario hace clic, Livewire llama al método increment() en el servidor y actualiza el HTML automáticamente.

Usar el componente

Puedes incluir el componente en cualquier vista Blade:

blade
<!-- Sintaxis de componente (recomendada) -->
<livewire:counter />

<!-- O con directiva Blade -->
@livewire('counter')

Data binding con wire:model

Una de las funcionalidades más potentes de Livewire es el data binding bidireccional. Conecta un input con una propiedad PHP y ambos se mantienen sincronizados:

php
<?php

declare(strict_types=1);

namespace App\Livewire;

use Livewire\Component;

class SearchUsers extends Component
{
    public string $search = '';

    public function render()
    {
        $users = User::where('name', 'like', "%{$this->search}%")
            ->take(10)
            ->get();

        return view('livewire.search-users', [
            'users' => $users,
        ]);
    }
}
blade
<div>
    <input type="text" wire:model.live="search" placeholder="Buscar usuarios...">

    <ul>
        @foreach($users as $user)
            <li>{{ $user->name }}</li>
        @endforeach
    </ul>
</div>

Con wire:model.live, cada vez que el usuario escribe, Livewire actualiza $search y re-renderiza la vista con los nuevos resultados. Todo sin escribir una línea de JavaScript.

Modificadores de wire:model: .live actualiza en tiempo real (cada tecla). .blur actualiza al perder foco. .debounce.300ms espera 300ms después de que el usuario deje de escribir.

Acciones con wire:click

Ya vimos wire:click en el contador. Puedes pasar parámetros a los métodos:

php
<?php

declare(strict_types=1);

namespace App\Livewire;

use App\Models\Post;
use Livewire\Component;

class PostList extends Component
{
    public function delete(int $postId): void
    {
        Post::findOrFail($postId)->delete();
    }

    public function render()
    {
        return view('livewire.post-list', [
            'posts' => Post::latest()->get(),
        ]);
    }
}
blade
<div>
    @foreach($posts as $post)
        <div>
            <h3>{{ $post->title }}</h3>
            <button wire:click="delete({{ $post->id }})">
                Eliminar
            </button>
        </div>
    @endforeach
</div>

Validación en Livewire

Livewire usa el mismo sistema de validación de Laravel que ya conoces:

php
<?php

declare(strict_types=1);

namespace App\Livewire;

use App\Models\Post;
use Livewire\Component;

class CreatePost extends Component
{
    public string $title = '';
    public string $content = '';

    protected array $rules = [
        'title' => 'required|min:5|max:255',
        'content' => 'required|min:10',
    ];

    public function save(): void
    {
        $validated = $this->validate();

        Post::create($validated);

        $this->reset(['title', 'content']);

        session()->flash('message', 'Post creado correctamente.');
    }

    public function render()
    {
        return view('livewire.create-post');
    }
}
blade
<div>
    @if (session()->has('message'))
        <div class="alert alert-success">
            {{ session('message') }}
        </div>
    @endif

    <form wire:submit="save">
        <div>
            <label>Título</label>
            <input type="text" wire:model="title">
            @error('title') <span class="error">{{ $message }}</span> @enderror
        </div>

        <div>
            <label>Contenido</label>
            <textarea wire:model="content"></textarea>
            @error('content') <span class="error">{{ $message }}</span> @enderror
        </div>

        <button type="submit">Guardar</button>
    </form>
</div>

Cuándo usar Livewire

Livewire brilla en ciertos escenarios, pero no es la solución para todo:

text
LIVEWIRE ES IDEAL PARA:
✓ Formularios dinámicos con validación en tiempo real
✓ Tablas con filtros, búsqueda y paginación
✓ CRUD sin recargar página
✓ Modales y dropdowns interactivos
✓ Dashboards con datos en tiempo real
✓ Carrito de compras y wizards multi-paso

CONSIDERA ALTERNATIVAS SI:
✗ Necesitas animaciones complejas (usa Alpine.js o CSS)
✗ La latencia de red es crítica (gaming, tiempo real intensivo)
✗ Ya tienes un equipo experto en Vue/React
✗ Tu app es una SPA pura que no necesita SEO

Livewire + Alpine.js: Livewire incluye Alpine.js por defecto. Para interacciones puramente frontend (mostrar/ocultar, animaciones), usa Alpine. Para interacciones con el servidor (guardar, buscar), usa Livewire. Ambos funcionan perfectamente juntos.

Resumen

  • Livewire permite crear interfaces interactivas usando solo PHP y Blade
  • Los componentes tienen una clase PHP y una vista Blade que trabajan juntas
  • wire:model sincroniza inputs con propiedades PHP automáticamente
  • wire:click llama métodos PHP cuando el usuario interactúa
  • La validación funciona igual que en Laravel tradicional
  • Es ideal para formularios, tablas dinámicas, CRUD y dashboards
  • Para interacciones solo frontend, combínalo con Alpine.js

Ejercicios

Ejercicio 1: Componente contador avanzado

Crea un componente Livewire llamado AdvancedCounter que tenga tres botones: incrementar, decrementar y resetear. El contador no debe poder ser negativo.

Ver solución
<?php
// app/Livewire/AdvancedCounter.php

declare(strict_types=1);

namespace App\Livewire;

use Livewire\Component;

class AdvancedCounter extends Component
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }

    public function decrement(): void
    {
        if ($this->count > 0) {
            $this->count--;
        }
    }

    public function reset(): void
    {
        $this->count = 0;
    }

    public function render()
    {
        return view('livewire.advanced-counter');
    }
}
<!-- resources/views/livewire/advanced-counter.blade.php -->
<div>
    <h2>Contador: {{ $count }}</h2>

    <button wire:click="decrement" @if($count === 0) disabled @endif>
        -1
    </button>

    <button wire:click="increment">
        +1
    </button>

    <button wire:click="reset">
        Resetear
    </button>
</div>

Ejercicio 2: Buscador de tareas

Crea un componente TaskSearch que muestre una lista de tareas (puedes usar un array hardcodeado) y permita filtrarlas en tiempo real mientras el usuario escribe en un input de búsqueda.

Ver solución
<?php
// app/Livewire/TaskSearch.php

declare(strict_types=1);

namespace App\Livewire;

use Livewire\Component;

class TaskSearch extends Component
{
    public string $search = '';

    public function render()
    {
        $tasks = collect([
            ['id' => 1, 'title' => 'Aprender Laravel'],
            ['id' => 2, 'title' => 'Crear proyecto con Livewire'],
            ['id' => 3, 'title' => 'Escribir tests'],
            ['id' => 4, 'title' => 'Desplegar a producción'],
            ['id' => 5, 'title' => 'Revisar documentación'],
        ]);

        $filtered = $tasks->filter(function ($task) {
            return str_contains(
                strtolower($task['title']),
                strtolower($this->search)
            );
        });

        return view('livewire.task-search', [
            'tasks' => $filtered,
        ]);
    }
}
<!-- resources/views/livewire/task-search.blade.php -->
<div>
    <input
        type="text"
        wire:model.live.debounce.300ms="search"
        placeholder="Buscar tareas..."
    >

    <ul>
        @forelse($tasks as $task)
            <li>{{ $task['title'] }}</li>
        @empty
            <li>No se encontraron tareas.</li>
        @endforelse
    </ul>
</div>

Ejercicio 3: Formulario con validación

Crea un componente ContactForm con campos para nombre, email y mensaje. Implementa validación en tiempo real (mientras el usuario escribe) y muestra un mensaje de éxito al enviar el formulario.

Ver solución
<?php
// app/Livewire/ContactForm.php

declare(strict_types=1);

namespace App\Livewire;

use Livewire\Component;

class ContactForm extends Component
{
    public string $name = '';
    public string $email = '';
    public string $message = '';
    public bool $submitted = false;

    protected array $rules = [
        'name' => 'required|min:2|max:100',
        'email' => 'required|email',
        'message' => 'required|min:10|max:1000',
    ];

    public function updated(string $propertyName): void
    {
        $this->validateOnly($propertyName);
    }

    public function submit(): void
    {
        $this->validate();

        // Aquí enviarías el email o guardarías en BD
        // Mail::to('admin@example.com')->send(new ContactMail($this->all()));

        $this->reset(['name', 'email', 'message']);
        $this->submitted = true;
    }

    public function render()
    {
        return view('livewire.contact-form');
    }
}
<!-- resources/views/livewire/contact-form.blade.php -->
<div>
    @if($submitted)
        <div class="alert alert-success">
            ¡Mensaje enviado correctamente!
        </div>
    @endif

    <form wire:submit="submit">
        <div>
            <label for="name">Nombre</label>
            <input type="text" id="name" wire:model.live="name">
            @error('name')
                <span class="error">{{ $message }}</span>
            @enderror
        </div>

        <div>
            <label for="email">Email</label>
            <input type="email" id="email" wire:model.live="email">
            @error('email')
                <span class="error">{{ $message }}</span>
            @enderror
        </div>

        <div>
            <label for="message">Mensaje</label>
            <textarea id="message" wire:model.live="message"></textarea>
            @error('message')
                <span class="error">{{ $message }}</span>
            @enderror
        </div>

        <button type="submit">Enviar</button>
    </form>
</div>

¿Quieres dominar Livewire?

Aprende a crear aplicaciones completas con Livewire 3, incluyendo componentes avanzados, formularios dinámicos y proyectos reales paso a paso.

Ver curso de Livewire 3