Laravel Livewire - Introducción al JavaScript

Video thumbnail

Una de las grandes características que tiene Laravel Livewire, es la integración del cliente con el servidor; específicamente en este contexto, PHP con Laravel con JavaScript; en este capítulo vamos a explorar más estas características.

Ya antes, veíamos una de estas estupendas integraciones mostrando el proceso de upload que incluye el Drag and Drop en Laravel Livewire.

Para emplear cualquier JS de Livewire como el objeto $wire o los hooks, debemos de emplear la siguiente directiva:

@script
<script>
   // JS Livewire
</script>
@endscript

El JavaScript de Livewire, es el generado por la directiva de:

@livewireScripts

Introducción a los Hooks en JavaScript

Los hooks, no son más que métodos que forman parte del ciclo de vida de los componentes, como presentamos anteriormente, en JavaScript, también tenemos referencias similares:

Hooks

morph.updating

morph.updated

morph.removed

morph.removing

morph.added

morph.adding

Para poder usar cualquiera de estas, tenemos que usar la siguiente función en JavaScript:

Livewire.hook(hookName, (...) => {})

Como mostramos en el listado anterior, con el objeto de JavaScript de Livewire, tenemos acceso a varios métodos tipo listeners, que se van a ejecutar como parte del ciclo de vida; veamos algunos:

@script
       <script>
           Livewire.hook('morph.updating', ({
               el,
               component,
               toEl,
               skip,
               childrenOnly
           }) => {
                console.log('morph.updating')
                console.log(component)
           })

           Livewire.hook('morph.updated', ({
               el,
               component
           }) => {
               console.log('morph.updated')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.removing', ({
               el,
               component,
               skip
           }) => {
               console.log('morph.removing')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.removed', ({
               el,
               component
           }) => {
               console.log('morph.removed')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.adding', ({
               el,
               component
           }) => {
               console.log('morph.adding')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.added', ({
               el
           }) => {
               console.log('morph.added')
               console.log(el)
           })
       </script>
   @endscript

Como recomendación para el script anterior, comenta todos los console.log() salvo al del hook que quieras probar.

Hooks en JavaScript

Video thumbnail

Vamos a probar aquí una especie de ciclo de vida como te comentaba que tenemos aquí del lado del cliente Recuerda que antes lo veíamos por acá todo lo que eran los métodos hidrate y de demás y ahorita es algo similar a eso pero en este caso del lado del cliente ya que ya va escrita entonces aquí te voy a pedir que vayas al repositorio y te copies este código o al menos que lo quieras escribir yo no lo voy a escribir no voy a perder 10 minutos en esto para alargar el video necesariamente pero básicamente:

@script
       <script>
           Livewire.hook('morph.updating', ({
               el,
               component,
               toEl,
               skip,
               childrenOnly
           }) => {
                console.log('morph.updating')
                console.log(component)
           })

           Livewire.hook('morph.updated', ({
               el,
               component
           }) => {
               console.log('morph.updated')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.removing', ({
               el,
               component,
               skip
           }) => {
               console.log('morph.removing')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.removed', ({
               el,
               component
           }) => {
               console.log('morph.removed')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.adding', ({
               el,
               component
           }) => {
               console.log('morph.adding')
               console.log(component)
               console.log(el)
           })

           Livewire.hook('morph.added', ({
               el
           }) => {
               console.log('morph.added')
               console.log(el)
           })
       </script>
   @endscript

Te comentaba antes tenemos métodos para cuando se están actualizando para cuando se están removiendo ya vamos a ver exactamente qué es lo que imprime por acá y cuando se están agregando lo que quieras hacer ahí depende de ti realmente algún script alguna función algún evento algo visual que quieras realizar lanzar un cohete a la luna no sé lo que tú quieras.

Lo primero es que si vengo acá que es la página recomendada para hacer este experimento que tenemos mucha movilidad Fíjate que no tenemos errores así que todo perfecto esto recuerda este esta directiva es para indicar cuando ya cargue el javascript de Livewire y ya podamos emplear características del Livewire.

Ya que obviamente esto no forma parte de la pi básico de javascript entonces nos daría un bonito error si el javascript de Livewire no ha sido cargado tal cual estás viendo en pantalla entonces ahí lo tienes que colocar y a partir de aquí si quieres emplear el objeto $wire por ejemplo, lo puedes hacer.

Colócalas el siguiente script:

@script
       <script>
         $wire
       </script>
@endscript

Ahora muévete en la paginación, actualice algo aquí aparecerían algunos mensajes en base a la acción realizada, si empleando el filtro se elimina el componente de paginación, entonces veras que se ejecuta el evento tipo remove y así para el resto.

Livewire y Alpine JS, los mejores aliados

Video thumbnail

En Livewire, al comparar con Blade, la cosa se complica un poco. Tenemos:

  • Servidor: Laravel (igual que en Inertia).
  • Cliente: Blade renderizado dinámicamente.

Cuando se devuelve el HTML al cliente, Blade ya genera todo el contenido. Sin embargo, Blade es principalmente una capa del servidor, y para aprovechar funcionalidades como la reactividad de Livewire en propiedades de componentes, necesitamos un framework adicional en el cliente.

No tendría sentido usar Vue aquí porque:

  • Ya estamos usando Blade.
  • Sería redundante si el proyecto no requiere un framework completo como Inertia.

Por esto, el equipo de Laravel optó por un framework ligero y no invasivo, como Alpine.js. Es modular, sencillo y se integra fácilmente con Blade y Livewire.

¿Qué es Alpine.js?

Alpine viene siendo una especie de Vue, por decirlo de alguna manera. Es un framework del lado del cliente que nos permite trabajar con estados, eventos y reactividad, pero de una forma muy ligera. Es un framework muy liviano, muy sencillo de integrar, y permite realizar operaciones como, por ejemplo, eventos click para cambiar estados y demás.

Es un framework reactivo, y eso es lo interesante al combinarlo con Livewire. Cuando hacemos clic en un elemento, el estado cambia automáticamente sin necesidad de que hagamos ese paso de forma manual.

Un primer acercamiento

Cuando empecemos a trabajar con Alpine vas a ver el sentido de todo esto. Este ejemplo introduce bastante bien qué es Alpine y cómo funciona.

Con Alpine nosotros podemos —y esta es una diferencia fundamental respecto a Vue— definir opcionalmente la lógica directamente en el HTML.

Por ejemplo, podemos definir:

  • los datos con los que vamos a trabajar
  • el alcance (scope) de esos datos
  • los métodos o funciones
  • y las propiedades reactivas

Todo esto directamente en el propio HTML, sin necesidad de archivos separados.

Ventajas de Alpine.js frente a Vue

Alpine.js permite:

  • Tener componentes interactivos en bloques específicos de HTML.
  • Mezclar HTML normal con funcionalidades reactivas de Alpine.js sin necesidad de renderizar toda la página con Vue.
  • Mayor flexibilidad: puedes usar Alpine.js en un componente, y en otro bloque usar otro framework o HTML puro.

Por ejemplo:

<html>
   <body>
       <div x-data="{ header: false }">
           Abrir un modal
       </div>
       <div x-data="{ fieldText: 'Pon tu nombre' }">
           Manejar un formulario
       </div>
       <div x-data="{ open: false }">
           Abrir un modal
       </div>
   </body>
</html>

En este ejemplo, cada bloque puede ser un componente Alpine.js independiente, sin afectar al resto de la página. Esto permite combinar HTML puro, Blade y Alpine.js en una misma página con gran flexibilidad.

En este otro ejemplo que estamos viendo, estamos definiendo la data dentro del HTML, asociada a un “componente” o bloque específico.
En ese scope —que en este caso es el <span>— podemos emplear la variable definida, por ejemplo: enable.

<span x-data="{ enabled: false }">
   <button @click.prevent="enabled = !enabled">Toggle</button>

   <template x-if="enabled">
       <span x-data="timer" x-text="counter"></span>
   </template>
</span>

Es decir, definimos la lógica aquí mismo y la usamos aquí mismo, lo cual es muy práctico para componentes pequeños y muy común cuando trabajamos junto con Livewire.

Integración con Livewire

Video thumbnail

Al usar Alpine.js junto con Livewire:

  • Se mantiene la reactividad en las propiedades de los componentes.
  • Se facilita la comunicación bidireccional entre cliente y servidor.
  • Podemos sincronizar cambios del cliente al servidor y viceversa, aprovechando la capa wire: de Livewire.

En comparación, Inertia solo nos devuelve un componente Vue en lugar de una página Blade, pero no permite comunicación bidireccional automática entre cliente y servidor como Livewire.

Mostrar y Ocultar Elementos con Alpine.js

En este caso, Alpine.js se está utilizando para mostrar u ocultar un contador que también tenemos definido en el HTML. Esto se logra mediante un condicional, que es bastante expresivo. Por ejemplo, estamos definiendo un evento click que ejecuta un toggle, es decir, invierte el estado: si algo se está mostrando, lo oculta, y si está oculto, lo muestra. Poco más que decir.

No hace falta que entiendas Alpine por completo ahora; todo lo que vayamos implementando te lo voy a ir presentando poco a poco. Pero sí es importante que tengas claro que Livewire viene con una integración incorporada con Alpine.

¿Por qué Alpine con Livewire?

Si revisas la documentación oficial, verás que Livewire incluye una sección específica para Alpine, ya que ambos se complementan muy bien.
Tiene todo el sentido del mundo: Livewire, por más práctico que sea, sigue siendo una tecnología del lado del servidor.

Entonces, ¿qué pasa con el cliente?
¿Qué ocurre cuando queremos:

  • ocultar elementos HTML,
  • aplicar animaciones,
  • generar transiciones suaves,
  • manipular el DOM directamente

Ahí es donde entra Alpine.

En Livewire, Alpine cumple en el cliente la misma función que cumple Vue en Inertia. Al usar Livewire, nos falta el “lado del cliente”, y para eso utilizamos Alpine, que viene perfectamente integrado por defecto.

Livewire + Alpine: la integración real

Aquí hay un punto importante: la documentación se queda un poco abstracta al decir “define tus datos así”, pero en realidad no es suficiente.

Porque si solo definimos los datos en Alpine, terminamos con:

  • Alpine por un lado
  • Livewire por otro

Y nosotros necesitamos que ambos mundos se comuniquen, especialmente cuando queremos acceder desde Alpine a propiedades expuestas por el componente de Livewire.

Por ejemplo, en el código del curso estamos exponiendo una propiedad llamada state (o step, según el ejemplo), que nos indica qué paso se debe mostrar. Esa es la data que necesitamos leer —o manipular— también desde Alpine.

Ahí es donde entra el objeto $wire, que es un puente entre Alpine y Livewire.
Con $wire podemos:

  • acceder a propiedades,
  • leer valores,
  • llamar métodos,
  • ejecutar operaciones asincrónicas,
  • emitir eventos,
  • entre otras cosas.

Todo esto está documentado en la sección de Livewire + Alpine.

Más adelante lo vamos a ver con calma, porque es el corazón de lo que estamos construyendo.

Nuestro primer componente Alpine

El primer candidato perfecto para usar Alpine es el bloque de selectores de pasos, ya que necesitaremos:

  • manipular qué paso está activo
  • actualizar las clases
  • reflejar cambios de forma reactiva

Aquí ya vimos que el diseño cambia según el paso seleccionado, así que eso mismo lo vamos a controlar con Alpine.

Definiendo el componente

El bloque que vamos a trabajar es el div que contiene los pasos.
Ese será nuestro componente Alpine.

Y, como siempre en Alpine, lo primero que definimos es:

La data es la información inicial del componente.
En nuestro caso, es simplemente el valor active:

<div class="flex" x-data="{ active: 1 }">

Este valor es local al cliente. Nada tiene que ver todavía con Livewire.
Más adelante sincronizaremos este valor con la propiedad de Livewire (state), pero por ahora es simplemente una variable de Alpine.

Alpine está diseñado para escenarios como este: componentes pequeños, lógicos, poco intrusivos. Para cosas más complejas, usaríamos Vue o React.

Aplicando clases de forma reactiva

Ahora evaluamos la clase active dinámicamente. La sintaxis es prácticamente la misma que en Vue:

<div class="step" :class="{ 'active': parseInt(active) == 1 }">
   {{ __('STEP 1') }}
</div>

Repetimos lo mismo para cada paso:

<div class="step" :class="{ 'active': parseInt(active) == 2 }">
   {{ __('STEP 2') }}
</div>
<div class="step" :class="{ 'active': parseInt(active) == 3 }">
   {{ __('STEP 3') }}
</div>

Si active es 3, se activa solo el paso 3.

Si vale 1, se activa el primero.

Y así sucesivamente.

Con JavaScript puro esto sería bastante más manual: localizar el elemento, quitar clases, buscar el siguiente, etc. Alpine nos lo da prácticamente gratis.

Evento click como ejemplo básico

Para introducirte un poco más en Alpine, podemos añadir un botón dentro del mismo x-data:

<button @click="active++">Incrementar</button>

Con esto:

al presionar el botón,

aumentamos el valor de active,

y automáticamente Alpine actualiza el DOM reactivo.

Si pusieras ese botón fuera del x-data, fallaría, porque Alpine no encontraría la variable en el “ámbito” correspondiente.

Conclusión

Alpine.js es:

  • Ligero, modular y fácil de aprender.
  • Perfecto para complementar Blade y Livewire.
  • Ideal para agregar interactividad en bloques específicos sin necesidad de frameworks pesados.

Con esto creo que queda claro dónde encaja Alpine en todo este sistema.
Ya en el siguiente paso veremos cómo conectar:

  • el valor inicial de Alpine
  • con la propiedad de Livewire (state)

Alpine JS y Laravel Livewire objeto $wire, entangle y sincronización de propiedades

Video thumbnail

La primera problemática es que necesitamos tener una referencia al step para poder mostrar el paso correcto según el valor definido en Livewire.

El segundo problema aparece cuando actualizamos el valor del step. Actualmente, debemos hacer clic manualmente para actualizar el estado en Alpine.js. La idea es que Livewire actualice automáticamente el estado en Alpine.js mediante reactividad, evitando la necesidad de interacciones manuales.

En resumen, ahora mismo tenemos dos "paredes":

  • Alpine.js completamente aislado de Livewire.
  • Livewire, con sus propiedades y eventos, sin reflejar los cambios automáticamente en Alpine.

El objeto $wire

Para resolver esto, Livewire nos proporciona el objeto $wire, que permite interactuar con las propiedades y métodos del componente desde el cliente. Algunas de sus funcionalidades son:

// Acceder a una propiedad del componente
$wire.foo
// Llamar a un método del componente
$wire.someMethod(param)
// Usar async/await para obtener resultados de un método
let foo = await $wire.getFoo()
// Emitir y escuchar eventos
$wire.emit('some-event', 'foo', 'bar')
$wire.on('some-event', (foo, bar) => {})
// Leer y actualizar propiedades
$wire.get('property')
$wire.set('property', value)

Para nuestro caso, solo necesitamos leer y sincronizar la propiedad step con Alpine.js.

Inicializando Alpine.js con Livewire

La forma básica de inicializar Alpine.js con el valor de Livewire es la siguiente:

<div class="flex" x-data="{ active: $wire.get('step') }">
  <div class="flex mx-auto flex-col sm:flex-row">
      <div class="step" :class="{ 'active': parseInt(active) == 1 }">
          {{ __('STEP 1') }}
      </div>
      <div class="step" :class="{ 'active': parseInt(active) == 2 }">
          {{ __('STEP 2') }}
      </div>
      <div class="step" :class="{ 'active': parseInt(active) == 3 }">
          {{ __('STEP 3') }}
      </div>
  </div>
</div>

⚠️ Esta inicialización solo se ejecuta una vez al cargar la página. No mantiene sincronización automática con Livewire.

Cómo verificar el valor de Livewire en Alpine

Para inspeccionar la propiedad step desde Alpine, podemos usar x-text:

<div x-text="$wire.step"></div>
<div x-text="$wire.get('step')"></div>

Ambas formas nos permiten imprimir el valor actual del step definido en Livewire.

Limitación de la inicialización

x-data="{ active: $wire.get('step') }" se ejecuta una sola vez.

Cambiar el valor de Livewire después no actualizará automáticamente Alpine.

Esto es similar al evento mount en Livewire, que solo inicializa valores al cargar el componente.

Solución: $wire.entangle()

Para lograr la sincronización bidireccional entre Alpine.js y Livewire, utilizamos $wire.entangle():

<div class="flex" x-data="{ active: $wire.entangle('step') }">
  <div class="flex mx-auto flex-col sm:flex-row">
      <div class="step" :class="{ 'active': parseInt(active) == 1 }">
          {{ __('STEP 1') }}
      </div>
      <div class="step" :class="{ 'active': parseInt(active) == 2 }">
          {{ __('STEP 2') }}
      </div>
      <div class="step" :class="{ 'active': parseInt(active) == 3 }">
          {{ __('STEP 3') }}
      </div>
  </div>
</div>

Ventajas de $wire.entangle():

Inicializa Alpine.js con el valor actual de Livewire.

Mantiene la sincronización automática: cualquier cambio en Livewire se refleja en Alpine y viceversa.

Permite observar la propiedad paso a paso, logrando un comportamiento reactivo y consistente.

Resultado final

Con $wire.entangle() logramos que:

  • El paso activo se muestre correctamente al cargar la página.
  • Cualquier actualización del step desde Livewire se refleje automáticamente en Alpine.js.
  • La integración entre el cliente (Alpine.js) y el servidor (Livewire) funcione de manera bidireccional.

Con esto, el step por step funciona perfectamente y se concluye la integración entre Alpine.js y Livewire.

Inyectar Vanilla JavaScript en wire:model de Laravel Livewire

Video thumbnail

Trabajar con Livewire simplifica muchas tareas en Laravel, pero cuando queremos usar JavaScript puro (vanilla JS) dentro de un componente, las cosas se complican. Livewire mantiene su propio sistema de reactividad, y eso hace que los cambios realizados directamente sobre el DOM no siempre se reflejen en las propiedades del componente.

Por qué combinar JavaScript nativo y Livewire puede ser un reto

El principal conflicto surge porque Livewire y el DOM no hablan el mismo idioma. En Livewire, cada campo que usa wire:model está vinculado a una propiedad en la clase del componente. Sin embargo, si se cambia el valor del campo desde JavaScript puro, Livewire no lo detecta automáticamente.

Cuando intenté modificar un campo con document.querySelector('#toc').value = 'vanilla JS';, parecía funcionar, pero la propiedad en la clase Livewire seguía con su valor anterior. Me di cuenta de que el cambio visual no implicaba una actualización del estado interno.

En resumen, existen dos capas independientes:

  • La vista: el campo visible en el formulario.
  • La clase: la propiedad que Livewire gestiona en segundo plano.

Ambas solo se sincronizan si el cambio ocurre mediante los mecanismos internos del framework. Por eso, al modificar el DOM directamente con JavaScript, el componente no reacciona.

Cómo inyectar JavaScript puro dentro de un componente Livewire

En este artículo explico cómo logré integrar código JavaScript nativo en un componente Livewire, sincronizando correctamente el valor de un campo mediante el evento input. El resultado es una forma práctica y limpia de combinar ambos mundos sin perder la reactividad.

Esto, en esencia, corresponde a un campo en Livewire. Tal como puedes ver, tenemos su componente, su clase y todo lo demás:

<input id="toc" class="block mt-1 w-full" wire:model="toc" />

El problema surge cuando quiero ejecutar un JavaScript X que genere algo y usarlo en un campo en particular; pero, desde Livewire NO podemos hacer este cambio directamente,  ya que es un proceso que depende más de JavaScript puro y tradicional. 

Mi idea es referenciar este campo llamado toc del índice y establecer de manera dinámica, apenas cargue la página, el valor correspondiente.

El análisis es sencillo. básicamente, convertí el árbol en un JSON y quise guardarlo en este campo. En primera instancia parece que funciona, pero si observamos con detalle, no es así:

document.querySelector('#toc').value == 'vanilla JS' 

Recordemos que tenemos dos capas:

  1. La vista: El campo o input en el formulario.
  2. La clase: La propiedad definida en la clase.

Estas no se sincronizan automáticamente si hacemos el cambio mediante solamente vanilla JS, por lo que de manera directa no funciona.

Aquí surge la pregunta: ¿por qué usar JavaScript nativo en lugar de depender del JavaScript que trae Livewire?

La respuesta es que el JavaScript de Livewire es muy cerrado: todo debe ejecutarse de forma local en el componente de Livewire, lo que mata un poco la modularización y des desde allí que pudieramos referenciar el JS de Livewire mediante el $wire o this.

Ejemplo práctico paso a paso

Existen varias alternativas. Por ejemplo:

Usar un componente en AlpineJS:

<input type="text" id="toc" x-data x-on:click="$wire.set('toc', 'aaa')">

Definir el valor de forma manual con más control.

Esta última opción fue la que apliqué, porque me permite generar el JSON, guardarlo en el campo y luego disparar un evento del navegador:

const json = JSON.stringify(data, null, 2);
input = document.querySelector('#toc')
input.value = json;
input.dispatchEvent(new Event('input'));

La clave: el evento input

Una vez hecho el cambio, lo que falta es disparar un evento con:

input.dispatchEvent(new Event("input"));

Esa última instrucción es la que realmente hace que Livewire reconozca el cambio. Al disparar manualmente el evento input, el framework interpreta que el usuario modificó el campo, y sincroniza la propiedad toc en la clase correspondiente.

Este pequeño detalle marca la diferencia: sin ese evento, el cambio es solo visual; con él, se mantiene la reactividad.

En mi caso, esta fue la única manera de lograr que Livewire actualizara la propiedad sin recurrir a métodos internos o dependencias adicionales.

En otras palabras, es como un pequeño hack que nos permite forzar la sincronización.

El secreto está en el evento input: sincroniza Livewire con el DOM

Frameworks como Vue, React y Livewire usan eventos del navegador para detectar cambios. El evento input es el que indica que el usuario escribió o modificó el contenido de un campo. Al dispararlo manualmente, engañamos (de forma legítima) al sistema de reactividad para que actualice su estado.

Este enfoque tiene la ventaja de mantener independencia total entre el JavaScript nativo y el código de Livewire. No es necesario acceder al componente con $wire ni usar referencias especiales. Además, permite integrar librerías o scripts externos que generen valores dinámicos (como un editor, un generador de JSON o un árbol de datos) y enviarlos directamente a Livewire.

Alternativas: AlpineJS, $wire.set() y eventos personalizados

Otra opción es usar AlpineJS, que ya viene integrado con Livewire y facilita la comunicación:

<input type="text" id="toc" x-data x-on:click="$wire.set('toc', 'aaa')">

Esta aproximación funciona bien, pero tiene menos control sobre los datos, especialmente cuando se necesita modificar valores generados dinámicamente o manejar objetos complejos. En mi caso, preferí hacerlo con JavaScript puro, porque me permitía manipular el DOM directamente y decidir cuándo sincronizar el estado.

También podrías disparar eventos personalizados o usar window.dispatchEvent() para notificar cambios más globales, aunque suele ser innecesario si el objetivo es simplemente actualizar un campo vinculado.

Consejos y buenas prácticas para integrar JavaScript en Livewire

  1. Evita modificar el DOM sin evento asociado. Livewire no lo detectará.
  2. Usa dispatchEvent(new Event('input')) cuando necesites forzar sincronización.
  3. Mantén el control local. No dependas del JavaScript interno de Livewire si buscas modularidad.
  4. Prueba con AlpineJS solo si tu cambio es trivial. Para procesos complejos, vanilla JS ofrece más control.
  5. Verifica el estado desde la consola. Usa $wire.get('propiedad') para comprobar que la sincronización fue exitosa.

FAQs sobre JavaScript y Livewire

¿Por qué Livewire no detecta cambios hechos con JavaScript?
Porque Livewire escucha eventos, no mutaciones directas del DOM. Si no disparas un evento, el framework no sabe que hubo un cambio.

¿Puedo usar JavaScript nativo sin AlpineJS?
Sí. Solo asegúrate de emitir el evento input después de modificar el valor del campo.

¿Qué diferencia hay entre $wire.set() y dispatchEvent('input')?
$wire.set() comunica el cambio directamente a Livewire, mientras que dispatchEvent() mantiene la lógica natural del DOM y puede integrarse mejor con scripts externos.

¿Funciona también con JSON o estructuras complejas?
Sí, siempre que conviertas los datos a texto (JSON.stringify()) antes de asignarlos al input.

Conclusión final

Integrar JavaScript nativo en Laravel Livewire es totalmente posible y, en muchos casos, recomendable. La clave está en entender cómo Livewire gestiona la reactividad y qué eventos utiliza para detectar cambios. En mi caso, disparar manualmente el evento input fue suficiente para sincronizar un campo dinámico generado desde JavaScript puro.

Con esta técnica, puedes combinar la potencia de Livewire con la flexibilidad del JavaScript tradicional sin perder control ni romper el ciclo de actualización del framework.

Evento enter de teclado wire:keydown.enter

Video thumbnail

La siguiente implementación que debemos realizar consiste en sincronizar las cantidades de un input con el ítem del carrito asociado. Para ello, emplearemos el evento enter, implementado de la siguiente manera:

<!-- resources/views/livewire/shop/cart-item.blade.php -->
<div>
  <div class="box mb-3">
      @if ($item)
          <p>
              <input wire:keydown.enter='add({{ $item }},$wire.count)' wire:model='count' class="w-20"
                  type="number">
              {{ $item->title }}
          </p>
      @endif
  </div>
</div>

Como se puede apreciar, se invoca el mismo método add() que utilizamos tanto para agregar, como para actualizar o eliminar items.

Por qué usamos $wire.count

En este caso, usamos $wire.count en lugar de:

<input wire:keydown.enter='add({{ $item }},{{ $count }})'>

Esto se debe a que el valor en el input aún no se ha actualizado en el servidor, por lo que si usamos $count, Livewire recibe el valor anterior, imposibilitando la actualización correcta de la cantidad del ítem.

En cambio, $wire.count obtiene directamente el valor del cliente, que es el que necesitamos actualizar.

Método add en Livewire

Podemos verificar el valor recibido en el servidor con:

// app/Livewire/Shop/CartItem.php
#[On('addItemToCart')]
function add(Post $post, int $count = 1) {
   dd($count);
   // resto de la lógica...
}

Esto nos permite evaluar y confirmar que estamos recibiendo el valor correcto desde el cliente.

Problema de condición de carrera

Existen dos posibles fuentes de actualización:

  • La actualización automática del servidor mediante wire:model u otras propiedades Livewire.
  • La actualización manual al presionar enter que dispara el método add().

Si ambas ocurren al mismo tiempo, podemos tener una condición de carrera:

  • ¿Cuál se ejecuta primero: el cambio en el cliente o la sincronización automática del servidor?
  • Normalmente, el evento enter se ejecuta antes que la actualización automática, lo que puede generar inconsistencias.
  • Esto explica por qué no podemos confiar únicamente en la actualización automática del wire:model en este caso.

Por qué no usamos wire:model.lazy o actualizaciones automáticas

  • Si empleamos wire:model.lazy, cada cambio en el input dispararía una actualización al servidor.
  • Esto sería ineficiente, especialmente si el usuario escribe varias cifras rápidamente (por ejemplo, 10, luego 100).
  • La lógica más eficiente es esperar al evento enter, que indica que el usuario ha terminado de modificar la cantidad.

Resumen de la lógica

  • Cambios a nivel de cliente: el usuario modifica el input, y el valor se mantiene en el cliente.
  • Evento enter: se dispara el método add() pasando $wire.count para enviar el valor actualizado al servidor.
  • Sincronización automática: Livewire detecta el cambio y actualiza la cantidad en el carrito.
  • De esta manera, mantenemos sincronización entre cliente y servidor, evitando condiciones de carrera y garantizando que la cantidad final refleje el valor ingresado por el usuario.

Siguiente paso, aprende a conocer los eventos personalizados de Laravel Livewire.

Acepto recibir anuncios de interes sobre este Blog.

Daremos una breve introducción de manera superficial para presentar las características más importantes del JS de Laravel Livewire.

| 👤 Andrés Cruz

🇺🇸 In english