Datos sobre las propiedades en Laravel Livewire

Video thumbnail

En esta entrada, veremos datos claves sobre las propiedades, aquellas que utilizamos en nuestros componentes de tipo formulario en Laravel Livewire.

Propiedades bloqueadas

A continuación se muestra un componente ShowPost que almacena el ID de un modelo de Post como una propiedad pública llamada $id. Para evitar que un usuario curioso o malintencionado modifique esta propiedad, puede agregar el atributo #[Locked] a la propiedad:

use Livewire\Attributes\Locked;
use Livewire\Component;

class ShowPost extends Component
{
   #[Locked] 
   public $id;

   public function mount($postId)
   {
       $this->id = $postId;
   }

   // ...
}

Al agregar el atributo #[Locked], se asegura de que la propiedad $id nunca será alterada.

Otra forma, sería declarando la propiedad como privada:

private $id;

Por defecto, cualquier propiedad pública expuesta en un componente puede ser manipulada desde la vista. Incluso un usuario malintencionado puede:

  • Abrir las herramientas de desarrollador.
  • Duplicar un campo que tenga wire:model.
  • Cambiar el nombre del campo a otra propiedad de la clase.
  • Modificar su valor y enviar ese payload hacia el backend.

Esto significa que un usuario podría intentar modificar, por ejemplo, un id, un role_id o cualquier propiedad sensible.

Restablecimiento de propiedades

También podemos restablecer propiedades empleando el método de $this->reset():

class ManageTodos extends Component
{
   public $todo = '';

   public function addTodo()
   { 
       $this->reset('todo'); 
   }
}

Tipos de propiedades admitidas

En la documentación oficial se aclara que los tipos soportados son aquellos que pueden convertirse fácilmente a JSON, debido a que Livewire se comunica mediante payloads serializados.

class TodoList extends Component
{
   public $todos = []; // Array

   public $todo = ''; // String

   public $maxTodos = 10; // Integer

   public $showTodos = false; // Boolean

   public $todoFilter; // Null
}

Opinión personal

Aunque no deja de ser curioso (o incluso un poco cutre, como digo yo) que un usuario pueda manipular wire:model desde la consola, la realidad es que es parte de la naturaleza reactiva del framework.

Así que si manejas propiedades que:

  • no deben cambiarse,
  • contienen IDs,
  • representan permisos,
  • exponen datos protegidos,

entonces #[Locked] es obligatorio.

Conclusión

Estas características —reset(), tipos válidos de propiedades, y el bloqueo con #[Locked]— completan la visión de cómo Livewire maneja sus propiedades internas y cómo debemos protegerlas cuando son sensibles.

Diferir actualización para los wire:model

Video thumbnail

Un aspecto crítico que tienes que tener en cuenta cuando trabajas con los wire:model, es que, por defecto al detectar cambios, Livewire envía mensajes al servidor cada pocos milisegundos para mantener la sincronización entre lo que existe en el cliente con el servidor y viceversa.

El problema con estas peticiones, es que, si se emplea la configuración por defecto, se envían demasiadas peticiones al servidor y si tenemos múltiples clientes conectados al mismo momento enviando este tipo de peticiones, puede dar problemas de rendimiento e inclusive errores del servidor ya que, cada solicitud hace que el servidor necesite más y más recursos que pueden que no estén disponibles debido a la elevada demanda de recursos.

Esto por suerte, ya no ocurre en las versiones modernas de Livewire a partir de la versión 3, por lo tanto, las opciones que tenemos aquí, NO aplican o no se pueden utilizar para las nuevas versiones de Laravel y se explican es de propósito netamente informativos.

Si buscamos en la documentación, veremos que:

  • lazy ya no aparece para wire:model.
  • defer tampoco funciona como antes.
  • debounce sí existe, pero con una sintaxis diferente y un comportamiento mucho más coherente.

Esto es importante porque la documentación antigua explica cosas que ya no aplican, y en la versión moderna la filosofía del framework cambió bastante.

Para evitar estos problemas, tenemos que definir características extras sobre nuestros wire:model que pueden ser de tres tipos; para estos ejemplos, puedes emplear cualquiera de los campos de formularios que existen actualmente para los de posts o categorías; por ejemplo:

resources/views/livewire/dashboard/post/save.blade.php

***
<div class="col-span-6 sm:col-span-4">
  <x-label for="">Title</x-label>
  <x-input-error for="title" />
  <x-input class="block w-full" type="text" wire:model="title" />
</div>
***

.debounce: Modifica el tiempo de actualización

Esta configuración modifica el tiempo tiempo de actualización del campo wire:model; por defecto es de 150 milisegundos; por ejemplo, para modificar este tiempo a 500ms:

<x-input class="block w-full" type="text" wire:model.debounce.500ms="title" />

Esta característica puede emplearse si estás usando la opción de live que permite actualizar la propiedad asociada al wire:model cada vez que escribimos, quedando como:

<x-input type="text" wire:model.live.debounce.500ms='title' class="w-full" />

.lazy: Envía actualizaciones al perder el foco

Esta configuración desactiva la sincronización del campo wire:model y únicamente actualiza el estado al perder el campo el foco.

<x-input class="block w-full" type="text" wire:model.lazy="title" />

.defer: Deshabilitar las actualizaciones

Esta configuración desactiva completamente la sincronización del campo wire:model; por lo tanto, los cambios realizados en dicho campo, se enviarán al ocurrir un envío de mensajes al servidor provistos por cualquier otro proceso de actualización en la aplicación.

<x-input class="block w-full" type="text" wire:model.deboun="title" />

Propiedad $refresh en los componentes de Laravel Livewire

Video thumbnail

Una de las complicaciones que yo veo con Livewire, es decir, un punto negativo si lo comparamos con Inertia o directamente con Laravel o la implementación en sí misma, es que la considero a veces muy abstracta y existen muchas formas de hacer lo mismo.

El Problema de la Consistencia en la Interacción
¿A qué me refiero con esto? Por ejemplo, en este caso, tenemos un sencillo problema. Tenemos un componente de detalle y el de Carrito incluido dentro del de detalle:

ShowComponent.php
@if ($post->type == 'advert')
   <div class="mycard mb-5 ms-auto block max-w-96">
       <div class="mycard-body">
           @livewire('shop.cart')
       </div>
   </div>
   @empty(session('cart')[$post->id])
   {{-- si el item esta en el carrito, removemos la opcion de agregar --}}
       <div class="mycard-primary mb-5 block max-w-96">
           <div class="mycard-body">
               <h3 class="text-center text-3xl mb-4">Add this item</h3>
               @livewire('shop.cart', ['post' => $post, 'type' => 'add'])
           </div>
       </div>
   @endempty
@endif

El problema es que, cuando interactuamos con el carrito de compras, queremos recargar el componente de Detalle, ya que hay otros componentes relacionados. Pero, no tenemos una forma directa.

Los componentes de componentes en Livewire son complejos

La solución, lamentablemente, no es tan sencilla, y es lo que yo digo que puede ser un poco abstracto todo lo que tenga que ver aquí con los componentes en Livewire. Ya que, pues nada, podría haber muchas formas de hacer lo mismo y puede que no quede tan claro qué es lo que tenemos que hacer: si recargar toda la página con JavaScript, si hacerlo mediante Alpine, que también existen algunas formas, si comunicar de alguna manera el componente, si utilizar las keys para redibujar el componente o directamente la propiedad de la cual te voy a hablar.

A lo que me refiero es que hay muchas formas de hacer lo mismo, y es bastante abstracto y complicado realmente de saber qué es lo que tenemos que hacer, que es un poco de lo que yo quiero hablar en las siguientes dos clases. Ya que, otra vez, esto es algo un poco trivial lo que queremos hacer, pero para llegar a ello realmente, al menos a mí, me cuesta un poquito.

Introducción a la Propiedad "Refresh"

Entonces, ¿qué es lo que quiero hablar sobre refresh? Ya aclarado un poco eso, ya que a lo que queremos llegar es a recargar ese componente. Entonces, para esto, fíjate que tenemos una propiedad que tampoco la referencian muy bien, llamada refresh. En este ejemplo, lo está utilizando desde un botón, por ejemplo:

   @endempty
   <button type="button" wire:click="$refresh">Refresh Demo</button>
@endif

Entonces, ahora veremos que recarga toda la página principal, incluyendo los componentes del carrito, que es lo que queremos.

Alternativas y la Comunicación entre Componentes

En este ejemplo, no estamos comunicando los componentes, sino directamente estamos recargando este. Otra forma, que sería un poquito más eficiente, sería redibujar esto mediante una key, que creo que vamos a ver algo de eso. Digo que sería un poquito más eficiente porque, obviamente, cuando estamos recargando, estamos haciendo una vez otra vez una petición al servidor y no va a recargar o no va a refrescar solamente esta parte de componentes, sino también lo que es este componente matriz. Pero en este caso, realmente nos importa poco porque es una publicación, pero bueno, como te digo, bastante abstracto todo esto.

El problema es que en ambos casos el componente y los componentes son hermanos, entonces, y todo está en el show, y tenemos y son independientes, pero por más que sea, esa acción no sucede directamente en el show, sino directamente en el componente hijo.

Tal vez con Alpine sí pudiéramos hacer algo, pero ahí es un poco lo comentado al inicio, se complica bastante la lógica simplemente para hacer esa recarga. Entonces, en estos casos, al igual que hice antes cuando estaba aquí recargando (que no sé qué, ahora apareció porque no me aparece esto en rojo, luego chequeo), cuando estamos haciendo aquí la "piratilla" que te dije de que queríamos colocar esto en rojo, a veces prefiero hacer ese tipo de implementaciones que no me están funcionando por alguna razón. No sé qué pasó, ahorita veo, en vez de estar aquí dándole mil vueltas ahí cómo se pudiera hacer, entre comillas, de la manera correcta, que estarías metiendo el pin, como quien dice, de la manera más eficiente. Entonces, antes de llegar a eso, prefiero recargar todo el componente.

Propiedades computadas en Laravel Livewire

Video thumbnail

Una funcionalidad interesante en Livewire es el uso de las propiedades computadas. Estas no son más que funciones definidas en el componente con la estructura get<NombrePropiedad>Property, y se consumen simplemente accediendo a <nombrePropiedad>. La principal particularidad de estas propiedades es que son útiles para derivar valores desde la base de datos u otro almacenamiento persistente, como un caché.

app/Http/Livewire/Dashboard/Category/Index.php

public function getCategoryProperty()
   {
       if ($this->categoryToDelete)
           return Category::find($this->categoryToDelete->id);
       return "Sin categoría seleccionada";
   }

Para consumir esta propiedad desde la clase o la vista, siempre debes utilizar $this:

$this->category

Puedes mostrar el valor en la vista de la siguiente manera:

resources/views/livewire/dashboard/category/index.blade.php

"Sin categoría seleccionada"

También puedes declarar una propiedad computada usando el atributo #[Computed], como se muestra a continuación:

// function getCategoryProperty()
#[Computed()]
function category() {
 ***    
}

Vamos a implementar una propiedad computada que devuelva la hora actual y compararla con un método tradicional para entender mejor su comportamiento:

#[Computed()]
function getTimeP()
{
   sleep(1);
   return time();       
}
function getTime()
{
   sleep(1);
   return time();    
}

Y en la vista:

{{ $this->getTimeP }}
{{ $this->getTimeP }}
{{ $this->getTimeP }}
{{ $this->getTimeP }}
{{ $this->getTimeP }}
<br>
{{$this->getTime()}}
{{$this->getTime()}}

Cuando se utilizan propiedades computadas, el valor se cachea automáticamente. Por lo tanto, verás que, sin importar cuántas veces se invoque, getTimeP devolverá el mismo valor, por ejemplo:

1728727286 1728727286 1728727286 1728727286 1728727286

En cambio, el método tradicional getTime() se ejecutará cada vez, así que obtendrás:

1728727287 1728727288

Esto demuestra que las propiedades computadas almacenan en caché el resultado hasta que cambia alguna de las propiedades internas que afectan su resultado, o se actualiza explícitamente.

Ventajas de las Propiedades Computadas

Método con lógica personalizada: Puedes construir una propiedad que internamente haga operaciones complejas, como consultas a la base de datos o cálculos derivados.

Cacheo automático: Una vez calculado el valor, este se guarda internamente. No se vuelve a ejecutar el método salvo que se detecte un cambio en sus dependencias o se actualice explícitamente.

Esto es ideal para operaciones costosas o cuando quieres evitar llamadas repetidas a servicios externos o a la base de datos.

class ShowUser extends Component
{
    public $userId;
 
    #[Computed]
    public function user()
    {
        $key = 'user'.$this->getId();
        $seconds = 3600; // 1 hour...
 
        return Cache::remember($key, $seconds, function () {
            return User::find($this->userId);
        });
    }
 
    // ...
}
class ShowUser extends Component
{
    public $userId;
 
    #[Computed]
    public function user()
    {
        return User::find($this->userId);
    }
}

<h1>{{ $this->user->name }}</h1>

El siguiente paso, es, aprender a crear un listado, un completo Datatable con ordenación en Laravel Livewire.

Acepto recibir anuncios de interes sobre este Blog.

Vamos a conocer otras características de las propiedades, tipos de propiedades admitidas, Restablecimiento de propiedades, Propiedades bloqueadas, computadas y sobre la propiedad de refresh.

| 👤 Andrés Cruz

🇺🇸 In english