Entendiendo los componentes de Laravel Livewire
Índice de contenido
- El concepto clave: estado + vista sincronizados
- Por qué Livewire se comporta como una SPA en Laravel
- Ejemplo de funcionamiento de componente
- ¿Qué pasa realmente al cambiar el idioma?
- ¿Por qué es importante este comportamiento?
- Cómo crear un componente Livewire (paso a paso)
- Crear el componente con make:livewire
- Propiedades públicas y binding con la vista
- Ciclo de vida de los componentes
- Métodos principales del ciclo de vida
- Resumen de métodos útiles
- Conclusión
- Componentes Principales en Livewire
- Componente de mostrar mensaje de acción realizada en Laravel Livewire
- Diálogo / Modal de alerta para eliminar: Componente de confirmación
- Preguntas frecuentes sobre los componentes de Livewire
- Conclusión
Qué es un componente Livewire y cómo funciona realmente
Cuando empecé a trabajar con Livewire, lo primero que me llamó la atención fue que se comporta como una SPA… pero sin escribir JavaScript. En cuanto entiendes su arquitectura, se vuelve evidente: Livewire actualiza solo lo que cambia. Nada más.
Un componente Livewire es básicamente la combinación de dos piezas:
- Una clase PHP que maneja el estado (propiedades y métodos)
- Una vista Blade que representa el UI de ese componente
Aunque en Volt ambas piezas estan unidas en un solo archivo.
La “magia” está en la sincronización. Cada vez que una propiedad cambia —ya sea porque el usuario hace clic, escribe algo, o el servidor ejecuta alguna acción— Livewire hace una única petición al backend, procesa el cambio y… redibuja solo ese componente.
En una de mis primeras pruebas, al cambiar un select de idioma, lo confirmé mirando la pestaña Network del navegador: aparece solo una petición. Nada más. Y lo mejor: el layout seguía intacto. Eso te muestra claramente que no estás recargando toda la página, sino solo la parte activa. Exactamente lo que hace una SPA.
El concepto clave: estado + vista sincronizados
El flujo es así:
- El usuario hace algo (click, submit, escribe, selecciona).
- Livewire envía un snapshot del estado al servidor.
- El servidor ejecuta métodos o actualiza propiedades.
- Livewire devuelve solo el HTML que cambió.
- El DOM se actualiza de forma inteligente (morphing).
Por qué Livewire se comporta como una SPA en Laravel
Livewire usa un algoritmo llamado DOM morphing, que compara el HTML viejo y el nuevo, y actualiza solo las diferencias. No sustituye todo el DOM, no recarga la página ni recompone el layout. Solo toca lo que cambió.
Esta eficiencia se nota especialmente en formularios, sistemas de filtros, paneles de usuario y dashboards.
Ejemplo de funcionamiento de componente
Por aquí quiero mostrarte algo que me pareció muy ilustrativo, para que entiendas exactamente cómo funcionan los componentes en Livewire y por qué realmente es una aplicación de tipo SPA (Single Page Application). Esto significa que no recarga toda la página, y no solo quedarnos con las características de un proyecto en Livewire que presentamos antes sino únicamente la parte que está cambiando, es decir, el componente con el cual estás trabajando. Al menos, claro, de manera básica.
Si tú haces uso del sistema de listeners para comunicar al padre, al abuelo, al bisabuelo, a la tía o al que sea, obviamente la cosa cambia. Pero me refiero al flujo normal, por así decirlo.
Tenemos un selector para el idioma. Fíjate que si cambio a “Español”, todo aparece en español:
<?php
namespace App\Livewire\User;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Illuminate\Support\Facades\App;
#[Layout('layouts.web')]
class UserProfile extends Component
{
public $language;
public function render()
{
if (!isset($this->language)) {
// al cargar el componente, este codigo se ejecuta al language NO estar seleccionado
// por el usuario
$this->language = session('locale') ?? App::getLocale();
} else {
// se ejecuta desde la vista asociada por el .live
session(['locale' => $this->language]);
App::setLocale($this->language);
}
return view('livewire.user.user-profile');
}
}Y la vista:
<div class="max-w-md m-auto mycard">
<div class="grid grid-cols-1 gap-2 my-3 mycard-body">
<flux:input :label="__('Email')" value="{{ auth()->user()->email }}" disabled readonly/>
<flux:input :label="__('Name')" value="{{ auth()->user()->name }}" disabled readonly />
<flux:select :label="__('Language')" id="language" wire:model.live="language" x-data @change="setTimeout(function(){window.location.reload()}, 100)" class="mt-1 block w-full rounded border-gray-300">
<option value="es">Español</option>
<option value="en">English</option>
</flux:select>
</div>
</div>Que es consumido mediante una ruta; adicionalmente, como puedes ver, el componente emplea un template llamado web.blade.php que tiene unos menus con textos a traducir.
Lo que hace el componente anterior, es establecer un nuevo idioma en la aplicación, al cambiar el select con el .live, hace que se REDIBUJE SOLO el componente. Si revisas el Network, verás que se hace una sola petición al servidor al cambiar el SELECT. Esto se logra usando wire:model.live, una funcionalidad muy útil si se usa inteligentemente (¡por favor, no lo pongas en todos los campos de texto porque vas a saturar el servidor!).
En este caso, tiene sentido para elementos como selects, donde quiero que se aplique un cambio de idioma en tiempo real.
¿Qué pasa realmente al cambiar el idioma?
Al cambiar el idioma, se ejecuta un método en el servidor (en Laravel, usando App::setLocale()), y le pasamos el nuevo idioma. En este ejemplo uso es para español y en para inglés. Ya tengo todas las traducciones configuradas desde el inicio del proyecto, así que se aplican inmediatamente.
El punto clave aquí es que SOLO recarga el componente, no toda la página. Esto lo puedes notar fácilmente porque al cambiar el idioma, el encabezado (parte del layout) no cambia, pero el contenido del componente sí.
¿Por qué es importante este comportamiento?
Esto es precisamente lo que pasa en una SPA: solo se actualiza la parte que realmente se necesita. Si yo quisiera que toda la página se recargara, podría emitir un evento al componente padre (o incluso al layout), pero en este caso no lo hice porque no era necesario. Tampoco creo que el usuario esté cambiando de idioma cada cinco segundos.
Cómo crear un componente Livewire (paso a paso)
La creación de un componente es directa. Laravel ofrece el comando:
$ php artisan make:livewire User/ProfileEsto genera:
app/Livewire/User/Profile.php (la clase del componente)
resources/views/livewire/user/profile.blade.php (la vista)
Crear el componente con make:livewire
También puedes crear componentes inline:
$ php artisan make:livewire user-profile --inlineAsí tienes clase y vista en un mismo archivo, útil para prototipos rápidos.
Estructura básica: clase PHP y vista Blade
Los componentes suelen contener:
- Propiedades públicas → forman parte del estado
- Método render() → define qué vista se usa
- Método mount() → inicialización del componente
- Métodos de acción → llamados desde la vista
- Omitir render() y usar convenciones cuando conviene
Si usas la estructura por defecto, Livewire sabe cuál vista corresponde a cada clase y puedes omitir render() totalmente.
Personalmente, en proyectos grandes prefiero mantenerlo visible para controlar el layout o inyectar datos a la vista, pero es opcional.
Propiedades públicas y binding con la vista
Aquí es donde Livewire brilla. Con solo declarar una propiedad pública, ya puedes sincronizarla con la vista:
- public $language;
- wire:model, wire:model.live y cuándo usar cada uno
- wire:model → sincroniza al perder el foco o al enviar el formulario.
- wire:model.live → sincroniza en tiempo real.
Ciclo de vida de los componentes
En Livewire, tenemos múltiples funciones que podemos emplear y que se llaman de manera automática según el estado del componente; esto sucede en muchos otros casos en otras tecnologías con en Android, Flutter, Vue… y estos por nombrar algunos; son sumamente útiles porque con ellos podemos realizar procesos que de otra forma serían complicados de definir; por ejemplo, inicializar propiedades como vamos a realizar luego cuando queramos editar un registro o categoría.
El ciclo de vida del componente. Esto hereda un poco lo que sería un controlador clásico en Laravel. En el esquema tradicional, no existe un ciclo de vida definido; únicamente contamos con el método constructor, que se ejecuta antes de cualquier otra acción cuando resolvemos la petición.
En Livewire, esto es más “vitaminizado”: los componentes tienen un ciclo de vida definido, muy similar a lo que ocurre en Vue, y podemos aprovecharlo para realizar varias acciones.
Para poder entender claramente que es lo que sucede cuando se ejecutan estos métodos del ciclo de vida, activa el log del sistema a nivel de archivo ya que, lo vamos a usar luego:
LOG_CHANNEL=singleCon los logs para los archivos, recuerda que se registran en:
'path' => storage_path('logs/laravel.log'),En nuestro componente de Save.php de las categorías; vamos a definir los siguientes métodos:
use Log;
***
public function boot()
{
Log::info("boot");
}
public function booted()
{
Log::info("booted");
}
public function mount()
{
Log::info("mount");
}
public function hydrateTitle($value)
{
Log::info("hydrateTitle $value");
}
public function dehydrateFoo($value)
{
Log::info("dehydrateFoo $value");
}
public function hydrate()
{
Log::info("hydrate");
}
public function dehydrate()
{
Log::info("dehydrate");
}
public function updating($name, $value)
{
Log::info("updating $name $value");
}
public function updated($name, $value)
{
Log::info("updated $name $value");
}
public function updatingTitle($value)
{
Log::info("updatingTitle $value");
}
public function updatedTitle($value)
{
Log::info("updatedTitle $value");
}Estos métodos, que forman parte del ciclo de vista, lo que hacen es imprimir en el log del sistema un mensaje que corresponde al nombre del método, y si el mismo recibe parámetros, también los registra en el log; estos parámetros son o valores o nombres y valores.
Como pruebas que debes realizar:
- Accede a tu log.
- Remueve cualquier log que exista en dicho archivo.
- Guardas los cambios en dicho archivo.
- Accede al componente de Save.php mediante tu navegador.
- Revisa el log.
- Da un click al botón de submit en el formulario.
- Revisa el log.
- Escribe algo en alguno de los campos de texto que tenga el wire:model.
- Revisa el log.
Métodos principales del ciclo de vida
De todos los métodos disponibles, el que más utilizamos en la práctica es mount.
- mount: Se ejecuta cuando se monta el componente. Es útil para inicializar datos, por ejemplo, para buscar un post o cargar información que el componente necesita.
- Existen otros métodos como boot, booted, updating, updated, hydrate, entre otros. Sin embargo, no los utilizamos en la mayoría de los casos, salvo para casos muy específicos de control avanzado o sincronización de datos.
La idea es que vayas haciendo pruebas, eliminando el contenido del log y probando componentes que, actualicen de manera directa las propiedades (como los campos de tipo wire:model), ingresar por primera vez al componente y dar clicks en botones que no actualicen los datos de los wire:model.
storage/logs/laravel.log
[2022-03-23 17:07:15] local.INFO: boot
[2022-03-23 17:07:15] local.INFO: mount
[2022-03-23 17:07:15] local.INFO: booted
[2022-03-23 17:07:15] local.INFO: render
[2022-03-23 17:07:15] local.INFO: dehydrate
[2022-03-23 17:07:18] local.INFO: boot
[2022-03-23 17:07:18] local.INFO: hydrateTitle
[2022-03-23 17:07:18] local.INFO: hydrate
[2022-03-23 17:07:18] local.INFO: booted
[2022-03-23 17:07:18] local.INFO: updating title asas
[2022-03-23 17:07:18] local.INFO: updatingTitle asas
[2022-03-23 17:07:18] local.INFO: updated title asas
[2022-03-23 17:07:18] local.INFO: updatedTitle asas
[2022-03-23 17:07:18] local.INFO: render
[2022-03-23 17:07:18] local.INFO: dehydrate Por supuesto, podemos aprovechar estas funciones para realizar procesos cruciales para nuestro componente, como por ejemplo, inicializar datos al crear un componente.
Aquí hay tres bloques principales:
- Las funciones de tipo update() se invocan cuando se actualizan las propiedades del componente, por ejemplo como el de wire:model; existen variantes como la de updated() y updating() cuyo funcionamiento es el mismo salvo que el de updating() se ejecuta antes.
- También tenemos la variante de update<Propiedad> la cual la podemos atar a una propiedad.
- Al ejecutar la función updated() la propiedad aún no ha sido actualizada, al ejecutar updated(), la propiedad ya fue actualizada.
- Funciones de hidrate() son usadas para cuando pasamos datos entre la vista y lo mapea a un objeto en PHP en la clase componente; por ejemplo, cuando se mapea las propiedades a un JSON para pasarlos a la vista para los wire:model:
- Las funciones de dehydrate() son usadas para justamente lo contrario de las de hidrate(), es decir, son usadas para cuando pasamos datos entre la clase componente a la vista; por ejemplo, cuando se mapea las propiedades a un JSON para pasarlos a la vista para los wire:model.
- En definitiva, estas funciones son para las actualizaciones a nivel del componente en el pase de mensaje y no solamente la data del mismo.
- Funciones del ciclo de vida básico como la de render(), boot(), o booted() que se ejecutan como parte del ciclo de vida al actualizar el componente.
- En este renglón también existe la función de mount() la cual se ejecuta solamente una vez al montar el componente, a diferencia de las anteriores y como puedes ver en el log señalado anteriormente.
Resumen de métodos útiles
- mount: Inicialización de datos. Es el que más usamos.
- render: Renderiza la vista del componente. Siempre se ejecuta cuando algo cambia.
- boot y booted: Se ejecutan al iniciar el componente, antes y después de mount. Pueden usarse para tareas de configuración general.
- updating y updated: Se ejecutan al modificar propiedades vinculadas a la vista (wire:model).
- hydrate y dehydrate: Se ejecutan al enviar datos entre el backend y el frontend, muy útiles cuando necesitamos manipular datos antes o después de la comunicación con la vista.
En general, en la mayoría de los casos solo necesitamos mount y render. El resto de los métodos son opcionales y se utilizan en situaciones más avanzadas.
Conclusión
Esto nos da una idea clara de cómo Livewire organiza la estructura de sus componentes y nos permite inicializar y controlar la data de forma eficiente.
No todos los métodos se usan en un desarrollo típico, pero es importante conocerlos y entender su propósito. Más adelante, si necesitamos alguna funcionalidad avanzada, podemos explorarlos más a fondo.
Con esto, damos por terminada esta clase. Todo el código estará disponible comentado en la sección correspondiente para que puedas experimentar y profundizar si lo deseas.
En conclusión, las funciones más usadas para el ciclo de vida de los componentes son las de update, en cualquiera de sus variantes y la de mount() para inicializar datos.
Componentes Principales en Livewire
En Livewire, tenemos muchos componentes que podemos implementar y reutilizar, para mi, es de lo mejor de Laravel Livewire veamos algunos.
Componente de mostrar mensaje de acción realizada en Laravel Livewire
Si estás empleando Livewire, te pudieras pregunta cómo podemos mostrar un mensaje por unos pocos segundos para que luego ocultarlo; esto es particularmente útil para cuando hacemos una operación de inserción, actualización, eliminación, el usuario se autenticó, etc; estos mensajes se conocer como mensajes de acción realizada, no son mensajes de confirmación en Livewire como vimos antes ya que, el usuario no podrá cancelar la acción realizada, solamente aparecerá un mensaje para que el usuario sepa que acción se acaba de realizar.
En Livewire se puede implementar este tipo de componentes muy fácilmente; pero para lograr algo de inspiración, tenemos un componente que es interesante de ver, y es:
https://github.com/laravel/jetstream/blob/2.x/resources/views/components/action-message.blade.php
El cual con ayuda de Alpine.js, permite determinar el tiempo de vida para visualizar este trozo de HTML:
<div x-data="{ shown: false, timeout: null }"
x-init="@this.on('{{ $on }}', () => { clearTimeout(timeout); shown = true; timeout = setTimeout(() => { shown = false }, 2000); })"
x-show.transition.out.opacity.duration.1500ms="shown"
x-transition:leave.opacity.duration.1500ms
style="display: none;"
{{ $attributes->merge(['class' => 'text-sm text-gray-600']) }}>
{{ $slot->isEmpty() ? 'Saved.' : $slot }}
</div>En la práctica podemos crear uno basado en el anterior como:
@if (session('status'))
<div x-data="{ shown: false }"
x-init="clearTimeout(2000); shown = true; timeout = setTimeout(() => { shown = false }, 2000);"
x-show.transition.out.opacity.duration.1500ms="shown" x-transition:leave.opacity.duration.1500ms>
<div class="p-1 mt-1 bg-purple-500 rounded-md">
<x-card class="m-0">
<h3 class="text-xl">
{{ session('status') }}
</h3>
</x-card>
</div>
</div>
@endifLo importante es emplear Alpine.js para ocultar el contenido mediante la función de setTimeout pasado cierto tiempo, por lo demás, ya conocemos como mostrar un componente de acción realizada que se muestra por pocos segundos.
Diálogo / Modal de alerta para eliminar: Componente de confirmación
Este apartado es una expansión del componente de alerta que mostramos anteriormente en otro capítulo:

La razón de esta expansión se debe a que queremos adaptar el módulo CRUD que creamos para las categorías para que se integre visualmente lo mejor posible; en Flux, tenemos un componente de Modal:
<flux:modal name="NAME">
*** CONTENT ***
</flux:modal>En el contenido, puedes colocar cualquier cosa, en nuestro caso, será un mensaje como el siguiente:
resources/views/livewire/dashboard/category/save.blade.php
<flux:modal name="delete-category">
<div class="m-1">
<flux:heading>{{ __('Delete Category') }}</flux:heading>
<flux:text class="mt-2">{{ __('Are you sure you want to delete this category?') }}</flux:text>
<div class="flex flex-row-reverse">
<flux:button class="mt-4" variant='danger' icon="trash">
{{ __('Delete') }}
</flux:button>
</div>
</div>
</flux:modal>Y el nombre, debe ser único, ya que es un identificador que emplearemos para accionar el modal; para accionarlo, tenemos varias formas, entre las principales, mediante un componente trigger, que es la que implementaremos:
resources/views/livewire/dashboard/category/save.blade.php
<a href="{{ route('d-category-edit', $c) }}">Edit</a>
<flux:modal.trigger name="delete-category">
<flux:button class="ml-3" variant='danger' size="xs">{{ __('Delete') }}</flux:button>
</flux:modal.trigger>Fíjaste que empleamos el nombre definido para el modal; para el diseño de botón, puedes personalizarlo a gusto.
También podemos activarlo desde la clase componente:
// Control "confirm" modals anywhere on the page...
Flux::modal('confirm')->show();
Flux::modal('confirm')->close();
// Control "confirm" modals within this Livewire component...
$this->modal('confirm')->show();
$this->modal('confirm')->close();
// Closes all modals on the page...
Flux::modals()->close();Mediante JS:
<button x-on:click="$flux.modal('confirm').show()">
Open modal
</button>
<button x-on:click="$flux.modal('confirm').close()">
Close modal
</button>
<button x-on:click="$flux.modals().close()">
Close all modals
</button>
***
// Control "confirm" modals anywhere on the page...
Flux.modal('confirm').show()
Flux.modal('confirm').close()
// Closes all modals on the page...
Flux.modals().close()O el objeto wire que conoceremos más adelante:
<flux:button x-on:click="$wire.showConfirmModal = true">Delete post</flux:button>Vamos a realizar la siguiente adaptación:
resources/views/livewire/dashboard/category/index.blade.php
***
<flux:modal name="delete-category">
***
<flux:button class="mt-4" variant='danger' icon="trash" wire:click="delete()">
{{ __('Delete') }}
</flux:button>
</div>
</div>
</flux:modal>Explicación del código anterior
La opción de eliminar una categoría la cual fue migrada al apartado de acciones, para cuando presionemos un botón de eliminar; aunque tiene un cambio importante, ya que, ahora no estamos pasando por referencia la categoría que queremos eliminar, esto lo establecemos desde antes, desde el apartarlo de los enlaces de acciones en la tabla de categorías:
<flux:modal.trigger wire:click="selectCategodyToDelete({{ $c }})" name="delete-category">
<flux:button class="ml-3" variant='danger' size="xs">{{ __('Delete') }}</flux:button>
</flux:modal.trigger>El siguiente paso, es, aprender a manejar los formularios en Laravel Livewire.
Preguntas frecuentes sobre los componentes de Livewire
¿Livewire recarga toda la página?
No, solo el componente afectado gracias al DOM morphing.
¿Cuándo usar wire:model.live?
Cuando la interacción requiere inmediatez: selects, sliders, búsquedas rápidas.
¿Qué diferencia hay entre un componente Blade y uno Livewire?
Blade es estático. Livewire es reactivo.
¿Livewire sigue siendo útil si sé JavaScript?
Sí. Añade fluidez a Laravel sin necesidad de levantar un ecosistema JS completo.
Conclusión
Los componentes Livewire combinan simplicidad con potencia. Te permiten crear interfaces reactivas sin frameworks externos, manteniendo todo en PHP. Y si aprovechas bien las propiedades, acciones, layouts y flujos internos, puedes construir desde paneles administrativos hasta sistemas completos en tiempo real.
En mi experiencia, entender cómo Livewire redibuja solo lo necesario fue el punto de inflexión para sacarle verdadero partido. Y detalles como no abusar de .live o usar wire:key correctamente marcan una diferencia enorme en rendimiento.
Acepto recibir anuncios de interes sobre este Blog.
Muestro un ejemplo de como funciona los componentes de Livewire, que es lo que recarga al momento de hacer el re render del mismo.