En la siguiente publicación vamos a ver un ejemplo simple de Livewire. Pero ¿Qué es Livewire?
Livewire es un framework, o mejor dicho un micro framework que funciona dentro del ecosistema de Laravel. Algo así como un framework dentro de otro.
Livewire se encarga de la parte frontend de las aplicaciones hechas con Laravel, y si bien podríamos resolver esto si tenemos conocimientos de alguna tecnología como puede ser React o Vue, o incluso Vanilla JS, con Livewire la cosa puede resultarnos mucho más sencilla, por lo menos dentro del universo de Laravel.
En primer lugar aclaro que todo lo que voy a hacer va a ser en base a la última versión de Laravel hasta la fecha. Si tenés dificultades con esto, te recomiendo leer la publicación que hice hace poco: Instalación de Laravel en Windows 11
Además también es recomendable tener conocimientos básicos de Laravel, sino nos resultará muy difícil entender lo que sigue a continuación.
Instalación
Les dejo el enlace de la publicación pasada, donde instalamos Laravel 12 con Livewire:
Para mostrar el ejemplo de esta publicación, vamos a crear un modelo y una migración para guardar una lista de tareas.
Abrimos una consola nueva y entramos en nuestro proyecto:
php artisan make:model Task -m
Entramos en database/migrations/codigo_generado_create_tasks_table y modificamos el método:
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
Por:
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title', 50);
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
Ejecutamos la migración:
php artisan migrate
Y por último vamos a app/Models/Task y agregamos la propiedad $fillable:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = ['title', 'completed'];
}
(Si no sabés para qué es esta propiedad, podés revisarlo en la documentación: https://laravel.com/docs/11.x/eloquent#mass-assignment)
Componentes
Si venís de otras tecnologías como React o Vue, seguramente tenés idea de esto.
Si en cambio no es así, un componente es una combinación de lógica y presentación. Podremos actualizar la información en tiempo real, sin tener que recargar la página.
Vamos a crear nuestro primer componente como nos indica la página de Livewire: https://livewire.laravel.com/docs/components
Escribimos por consola:
php artisan make:livewire NombreDelComponente
En NombreDelComponente es justamente el nombre descriptivo que queremos ponerle al componente que se encargará de gestionar las tareas. Por ejemplo:
php artisan make:livewire HandleTasks
Al ejecutar lo anterior nos va a generar dos archivos, una clase (lógica del componente) y una vista (visualización del componente)
Las clases por defecto se guardan en app/Livewire, mientras que las plantillas en resources/views/livewire
Si abrimos estos dos archivos, es decir: HandleTasks.php y handle-tasks.blade.php podemos ver que la clase tiene un método llamado render que retorna la plantilla:
public function render()
{
return view('livewire.handle-tasks');
}
Es importante entender que este método va a renderizarse cada vez que detecte un cambio, actualizando el código de la plantilla, como vamos a ver en un rato.
Incluir el componente en nuestro proyecto
Podemos probar el funcionamiento de nuestro proyecto, incluyéndolo en cualquier vista de la siguiente manera:
<livewire:handle-tasks />
(handle-tasks es el nombre del componente que estamos incluyendo, si pusiste otro nombre, tendrás que ingresar ese mismo)
Agregar ruta para componente
Otra posibilidad que nos da Livewire es la de crear una url para un componente, igual que lo hacemos con los controladores tradicionales de Laravel.
Para eso vamos a routes/web.php
Incluimos el componente con use:
use App\Livewire\HandleTasks;
Y lo agregamos con una ruta:
Route::get('/tasks', HandleTasks::class)->name('tasks');
Si ingresamos a nuestra ruta http://127.0.0.1:8000/tasks, seguramente nos devuelva un error, porque los componentes siempre deben ir dentro de un layout.
Para eso vamos a abrir nuestro archivo HandleTasks.php y modificar:
public function render()
{
return view('livewire.handle-tasks');
}
Por:
public function render()
{
return view('livewire.handle-tasks')->layout('layouts.app');
}
Para crear un layout aparte para nuestro componente, podemos revisar la documentación de Livewire:
https://livewire.laravel.com/docs/components#layout-files
Propiedades
En Livewire las propiedades nos permiten almacenar información que estará disponible y sincronizada tanto en la clase, como en la plantilla:
HandleTasks.php (clase):
public $title = 'Título de la tarea';
handle-tasks.blade.php (platilla)
<div>
{{ $title }}
</div>
Teniendo en cuenta esto, podemos agregar una tarea nueva sin tener que recargar la página.
Agregamos un método en la clase:
//Agrega una tarea nueva.
public function add()
{
Task::create([
'title' => $this->title
]);
//Limpiamos el valor de la propiedad.
$this->title = '';
}
La clase finalmente va a quedar:
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Task;
class HandleTasks extends Component
{
public $title = '';
//Agrega una tarea nueva.
public function add()
{
Task::create([
'title' => $this->title
]);
//Limpiamos el valor de la propiedad.
$this->title = '';
}
public function render()
{
//Incluimos la lista de tareas.
$tasks = Task::get();
return view('livewire.handle-tasks', [
'tasks' => $tasks
])->layout('layouts.app');;
}
}
Y dentro de la plantilla vamos a agregar un formulario con la propiedad y la acción para agregar una tarea nueva:
<!-- Formulario para agregar tarea nueva -->
<form wire:submit="add">
<input type="text" wire:model="title">
<button type="submit"> Agregar tarea </button>
</form>
<!-- Lista de tareas -->
Notar el uso en las plantillas de wire:submit, en donde tenemos que indicarle el método que va a llamar al enviarse la información del formulario. Aclarar que este formulario se va enviar de forma asíncrona, es decir que la página no va a recargarse, y tampoco debemos indicarle la URL, sino que Livewire se encargará solo de enviar esa información con la acción del método add()
Y también algo muy importante que es wire:model, para que la propiedad «title», se llene con lo que haya ingresado el usuario por teclado.
Además vamos a agregar un foreach para mostrar las tareas agregadas:
<!-- Lista de tareas -->
<ul class="mt-3">
@foreach ($tasks as $task)
<li>
{{ $task->title }}
</li>
@endforeach
</ul>
El código finalmente quedará así:
@once
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Tareas') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<!-- Formulario para agregar tarea nueva -->
<form wire:submit="add">
<input type="text" wire:model="title">
<button type="submit"> Agregar tarea </button>
</form>
<!-- Lista de tareas -->
<ul class="mt-3">
@foreach ($tasks as $task)
<li>
{{ $task->title }}
</li>
@endforeach
</ul>
</div>
</div>
</div>
</div>
@endonce
Sub componentes
A medida que nuestro componente crezca, se volverá más caótico, por tanto, en ciertas ocasiones es recomendables el código que pueda separarse.
Por ejemplo vamos a crear un componente aparte para manejar cada una de las tareas de forma individual.
Creamos un componente nuevo:
php artisan make:livewire HandleTaskItem
Agregamos a la clase HandleTaskItem.php el objeto Task para manejar individualmente los datos de ésta, dependiendo de la tarea que renderice:
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Task;
class HandleTaskItem extends Component
{
public Task $task;
//Cambia el estado de la tarea.
public function change()
{
$completed = !$this->task->completed;
$this->task->update([
'completed' => $completed
]);
}
public function render()
{
return view('livewire.handle-task-item', [
'task' => $this->task
]);
}
}
Y modificamos la plantilla handle-task-item.blade.php:
<li>
<input type="checkbox" @checked($task->completed) wire:change="change()" />
{{ $task->title }}
</li>
Por último vamos a ir a la otra plantilla, es decir la del componente padre handle-tasks.blade.php, y modificar:
@foreach ($tasks as $task)
<li>
{{ $task->title }}
</li>
@endforeach
Por:
@foreach ($tasks as $task)
<li>
<livewire:handle-task-item
:task="$task"
:key="$task->id"
/>
</li>
@endforeach
Como podemos ver en el código, tenemos que incluir dentro de cada vuelta de un foreach la llamada a un componente y pasarle las propiedades por fuera. Con respecto a key, es una directiva de Livewire para identificar a cada elemento dentro del bucle. Si no la usamos, funcionará igual, pero puede traer comportamientos indeseados.
Evento dispatch
En ciertos ocasiones puede ser que queramos enviar mensajes de un componente a otro.
Supongamos que cuando el componente hijo modifica su estado con el método change(), queremos que en el componente padre suceda algo.
Vamos a HandleTaskItem.php:
Y modificamos el método change():
//Cambia el estado de la tarea.
public function change()
{
$completed = !$this->task->completed;
$this->task->update([
'completed' => $completed
]);
//Enviamos un evento con el nombre task-updated.
$this->dispatch('task-updated');
}
Si necesitamos agregarle información para que llegue al otro componente podemos también pasarle un segundo argumento, porque ejemplo para que el componente padre sepa de qué tarea se trata:
$this->dispatch('task-updated', $this->task);
Volvemos a HandleTasks.php e importamos:
use Livewire\Attributes\On;
Y agregamos un método que va a quedar escuchando cada cambio del componente hijo:
#[On('task-updated')]
public function updatePostList(Task $task)
{
//dd("Se ha modificado la tarea: {$task->title}");
}