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:
composer require livewire/livewire
Para que Livewire funcione, necesitas un layout donde se renderizarán tus componentes. Puedes generarlo automáticamente con Artisan:
php artisan livewire:layout
Este comando crea el archivo
resources/views/components/layouts/app.blade.php:
<!-- 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:
php artisan make:livewire Counter
Este comando crea dos archivos:
app/Livewire/Counter.php- La clase del componenteresources/views/livewire/counter.blade.php- La vista
La clase del componente
<?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
<!-- 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:
<!-- 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
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,
]);
}
}
<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
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(),
]);
}
}
<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
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');
}
}
<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:
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>
¿Has encontrado un error o tienes una sugerencia para mejorar esta lección?
Escríbenos¿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