Manejo de formularios en Laravel Livewire mediante un componente

Video thumbnail

Livewire gestiona la comunicación entre tu vista y tu componente en Livewire enviando pequeñas peticiones AJAX. Tú solo actualizas propiedades públicas y Livewire hace el resto. Veamos como crear nuestro primer componente.

Enlace de datos con wire:model y ciclo de vida del componente

El atributo wire:model crea un enlace reactivo entre el input y la propiedad del componente.
Cambias el input → se actualiza la propiedad.
Cambias la propiedad → se actualiza el input.

Envíos tipo AJAX y manejo automático de errores

Solo añades wire:submit.prevent="submit" y Livewire se encarga del envío, del bloqueo del botón, de la validación y del refresco parcial de la vista.

Para crear un formulario básico en Livewire, primero debes crear un componente Livewire y luego definir las propiedades públicas que contendrán los datos del formulario. Entonces, debes crear un método para manejar el envío del formulario y validar los datos utilizando las funciones de validación de Laravel. Finalmente, debes agregar el marcado HTML del formulario con los atributos wire:model para enlazarlos con las propiedades públicas.

En este artículo, vamos a crear un componente en Livewire, que son la estructura que tenemos que usar para poder aprovechar las bondades principales de Laravel Livewire.

Recuerda que para poder continuar, debes saber como crear un proyecto en Laravel Livewire.

Crear un formulario básico en Livewire paso a paso

Creamos el componente con:

php artisan make:livewire Dashboard/Category/Save

Estas propiedades representan los campos del formulario:

public $title;
public $text;
public $image;

Creamos la ruta con:

  Route::group(['prefix' => 'category'],function () {
        Route::get('/create', App\Http\Livewire\Dashboard\Category\Save::class)->name("d-category-create");  // crear
        Route::get('/edit/{id}', App\Http\Livewire\Dashboard\Category\Save::class)->name("d-category-edit");// edit
    });

Y el modelo luce como el siguiente:

class Category extends Model
{
    use HasFactory;
    protected $fillable=['title','slug','image','text'];
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
    public function getImageUrl()
    {
        return URL::asset("images/".$this->image);
    }
}

En el vista de componente, definimos nuestro formulario para la gestión de las categorías:

<form wire:submit="submit">
    <label for="title" @class(['error' => $errors->has('title')])>Título</label>
    <input type="text" id="title" wire:model.blur="title">
    @error('title')
        <p class="text-danger">{{ $message }}</p>
    @enderror

    <label for="text" @class(['error' => $errors->has('text')])>Texto</label>
    <input type="text" id="text" wire:model.blur="text">
    @error('text')
        <p class="text-danger">{{ $message }}</p>
    @enderror

    <button type="submit">Enviar</button>
</form>

Y en la clase del componente:

namespace App\Http\Livewire\Dashboard\Category;
use App\Models\Category;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
use Livewire\WithFileUploads;
class Save extends Component
{
    use WithFileUploads;
    public $title;
    public $text;
    public $image;
    public $category;
    protected $rules = [
        'title' => "required|min:2|max:255",
        'text' => "nullable",
        'image' => "nullable|image|max:1024",
    ];
    public function mount($id = null)
    {
        if ($id != null) {
            $this->category = Category::findOrFail($id);
            $this->title = $this->category->title;
            $this->text = $this->category->text;
        }
    }
    public function render()
    {
        //Log::info("render");
        return view('livewire.dashboard.category.save');
    }
    public function submit()
    {
        // validate
        $this->validate();
        // save
        if ($this->category){
            $this->category->update([
                'title' => $this->title,
                'text' => $this->text,
            ]);
            $this->emit("updated");
        }else{
           $this->category = Category::create(
            [
                'title' => $this->title,
                'slug' => str($this->title)->slug(),
                'text' => $this->text,
                ]
            );
            $this->emit("created");
        }
        // upload
        if($this->image){
            $imageName = $this->category->slug . '.' . $this->image->getClientOriginalExtension();
            $this->image->storeAs('images', $imageName,'public_upload');
    
            $this->category->update([
                'image'=> $imageName
            ]);
        }
    }
}

Fíjate que en la función de submit manejamos ambos procesos, el de crear y editar según el estado de una categoría que existe solo si estamos en la fase de edición y que inicializamos en la fase del mount del componente.

Estas son las faces de cualquier proceso CRUD y en Livewire son procesos muy directos como ya hemos apreciado, vamos a verlo por parte:

Uso un arreglo $rules, como en tu propio código:

protected $rules = [
   'title' => "required|min:2|max:255",
   'text' => "nullable",
   'image' => "nullable|image|max:1024",
];

Validación en tiempo real con updated()

Si quieres validar mientras el usuario escribe:

public function updated($propertyName)
{
   $this->validateOnly($propertyName);
}

Integrar FormRequest en Livewire (caso avanzado)

Puedes reutilizar tu FormRequest y pedirle las reglas dentro del componente:

use App\Http\Requests\CategoryRequest;
public function rules()
{
   return (new CategoryRequest)->rules();
}

Mostrar campos de formulario en Laravel Livewire condicionalmente: Paso por paso

Video thumbnail

Recientemente me preguntaron cómo podríamos mostrar un campo de forma condicional. Por ejemplo, si aquí selecciono cualquier valor —supongamos que selecciono "persona"—, me gustaría que aparezca un campo adicional. Así que, para esto, hay varias consideraciones que debemos tener en cuenta.

Voy a partir del ejemplo paso por paso que hicimos en el curso de Livewire, aunque perfectamente lo puedes adaptar a un formulario normal sin pasos. La lógica es la misma, ya que este sistema paso a paso que implementamos es bastante modular —como siempre he comentado.

Consideraciones iniciales

Lo primero es que, si vas a crear la migración (o supongo que como es condicional...), entiéndase que si aquí seleccionamos "compañía" —porque "persona" es el valor por defecto— entonces quiero mostrar ese campo adicional. Si es "persona", no mostraré nada.

Esto es simplemente una condición que se puede evaluar mediante un condicional, así que puede ser cualquier cosa.

Obviamente, ese campo adicional debe existir en la entidad, por lo que tendrías que crearlo. En mi caso no lo voy a crear porque esto es solo una demostración, así que no me importa si se guarda o no en la base de datos. Pero lo lógico es que lo guardes, porque si no, no estás haciendo nada con ese campo.

Ese campo extra debería estar definido también en el modelo, y se puede marcar como nullable, o puedes definir un valor por defecto si no está establecido. Eso ya depende de ti y de cómo quieras manejarlo. Pero, en resumen, no tiene mucho misterio.

Validaciones condicionales en formularios

Aquí lo más recomendable es que utilices clases de tipo formulario con el método rules(), ya que ahí puedes definir las reglas de validación condicionales. Por ejemplo, si cierto campo tiene determinado valor, puedes aplicar o no aplicar ciertas validaciones.

En este caso, yo no lo hice así. Si revisamos la clase, las validaciones están definidas directamente, por lo que no puedo hacer mucho en este ejemplo. Pero repito: usar clases de formulario con rules() es lo mejor, ya que puedes evaluar condiciones y aplicar o no validaciones según lo necesites.

También podrías aplicar validaciones directamente usando el método validator() —si mal no recuerdo, hay como tres formas—. Pero, como ya tengo esta estructura, no quiero cambiar mucho para respetar lo que hicimos en el curso. Esto es solo una demostración.

Implementación básica en el componente

En este caso, estoy utilizando Volt, ya que es el que me está funcionando bien. El otro componente parece tener problemas con los eventos.

Lo primero es definir el nuevo atributo del campo. Voy a llamarlo extra. Aquí deberías tener también sus validaciones. Como es una demo, solo lo defino por simplicidad.

Luego, lo pintamos en el HTML. Lo coloco justo después del campo de tipo, y puede ser cualquier tipo de campo, pero usaré otro select. Lo llamo extra.

new #[Layout('layouts.contact')] class extends Component {
    ***

    #[Validate('required')]
    public $type;
    
    // demo
    public $extra;

    ***

    function mount(?int $id = null, ?int $step = null, string $subject = '')
    {
        ***
    }

    public function updated($property)
    {
        // $property: The name of the current property that was updated

        if ($property === 'type' && $this->type != 'company') {
            $this->extra = '';
        }
    }
    
    function submit()
    {
       ***
    }
}; ?>

<div>
   ***
        @if ($step == 1)
            <form wire:submit.prevent='submit' class="flex flex-col max-w-sm mx-auto">
                ***

                <flux:label>{{ __('Type') }}</flux:label>
                <flux:error name='type' />
                <flux:select wire:model.live='type'>
                    <option value=""></option>
                    <option value="person">{{ __('Person') }}</option>
                    <option value="company">{{ __('Company') }}</option>
                </flux:select>
                @if ($type == 'company')
                    <flux:select wire:model='extra' :label='__("Extra")'>
                        <option value=""></option>
                        <option value="extra1">{{ __('Extra 1') }}</option>
                        <option value="extra2">{{ __('Extra 2') }}</option>
                    </flux:select>
                @endif

                <flux:label>{{ __('Message') }}</flux:label>
                ***

                <div class="flex mt-5 gap-3">
                    <flux:button variant='primary' type='submit'>{{ __('Send') }}</flux:button>
                </div>
            </form>
    </div>
</div>

Ocultar el campo condicionalmente

Por defecto, no quiero que aparezca el campo. Aquí colocamos la condición para mostrarlo: si el tipo seleccionado es igual a "company", entonces mostramos el campo.

@if ($type === 'company')
   <!-- Mostrar el campo extra -->
@endif

Esto lo puedes imprimir para verificar. Por ejemplo, seleccionas "persona", no se muestra nada. Seleccionas "compañía", aparece el campo extra. Esto se logra agregando .live, que hay que usar con cuidado.

Pasar parámetros del padre a los hijos

Video thumbnail

Como comentamos anteriormente, el pase de los eventos del padre al hijo para la implementación que tenemos actualmente, no va a funcionar ya que, al momento de que se envía el evento del padre los componentes hijos aún no han sido creados (un posible enfoque para poder emplear los eventos es ocultar los formularios del paso por paso mediante CSS y mostrarlos en base al paso actual; aun así, el uso de los eventos no es necesario para enviar datos de un componente padre a los hijos, perfectamente podemos emplear el suministros de parámetros en su lugar como vemos a continuación:

@elseif ($step == 2)
   @livewire('contact.company', ['parentId' => $pk])
@elseif ($step == 2.5)
   @livewire('contact.person', ['parentId' => $pk])
@elseif ($step == 3)
   @livewire('contact.detail', ['parentId' => $pk])

Y definimos el método de mount en cada uno de los pasos hijos:

app/Http/Livewire/Contact/Company.php
app/Http/Livewire/Contact/Person.php
app/Http/Livewire/Contact/Detail.php
function mount($parentId)
{
  $this->parentId($parentId);
}

Limpieza del valor cuando cambia la opción

El problema es que si seleccionamos "persona" después de haber seleccionado "compañía", el valor del campo extra queda definido. Para limpiar esto, se puede usar un método del ciclo de vida de Livewire, como updated.

Este método recibe la propiedad que se ha actualizado. Por ejemplo:

    public function updated($property)
    {
        // $property: The name of the current property that was updated

        if ($property === 'type' && $this->type != 'company') {
            $this->extra = '';
        }
    }

Esto limpia el campo extra automáticamente cuando se cambia el tipo a algo diferente de "compañía".

Validaciones locales (opcional)

Todavía no tenemos validación en esta demo. Como la estructura actual no permite validación condicional fácilmente, podrías aplicar una validación local manual: cuando hagas submit, verificas si el campo extra está definido, y lo validas en ese momento.

Clase/Objeto formulario en Laravel Livewire, simplifica tus Componentes

Video thumbnail

Esto me parece muy interesante: la posibilidad de separar la definición de cada uno de los campos del formulario en un archivo aparte.

Una de las cosas que critico en silencio de Livewire —sí, lo admito— es precisamente eso: que no podemos crear clases Request como lo hacemos normalmente en Laravel para definir los campos y las validaciones del formulario.

En lugar de eso, tenemos que hacer las validaciones directamente en el componente. Y sí, eso funciona, pero:

  1. Mezcla lógica de validación con lógica de presentación,
  2. Hace los componentes más largos y difíciles de mantener,
  3. Y rompe un poco con la forma tradicional y limpia de trabajar con formularios en Laravel.

Veamos un código:

class Save extends Component
{

    use WithFileUploads;

    public $title;
    public $slug;
    public $text;
    public $image;

    protected $rules = [
        'title' => "required|min:2|max:255",
        'text' => "nullable",
        'image' => "nullable|image|max:1024",
    ];

Imposibilitando la reutilización y modularización del componente, pero, mediante una clase formulario, podemos solventar esto fácilmente.

Formularios en Laravel Livewire

Para crear una clase formulario, que es equivalente al Request para los controladores:

$ php artisan livewire:form PostForm

Colocamos nuestros campos:

<?php
namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:2|max:255')]
    public $title = '';
    
    #[Validate('required')]
    public $date = '';
    
    #[Validate('required')]
    public $category_id = '';
    
    #[Validate('required')]
    public $posted = '';

    #[Validate('required')]
    public $type = '';
    
    #[Validate('required|min:2|max:5000')]
    public $text = '';

    #[Validate('required|min:2|max:255')]
    public $description = '';

    #[Validate('nullable|image|max:1024')]
    public $image = '';
}

En este caso según el ejemplo tenemos que emplear el atributo de validate eso lo tenemos que migrar.

Ahora, nuestro componente y vista quedan mucho más limpias de la siguiente forma, usando una instancia de la clase anterior y no una propiedad por cada atributo:

El componente queda como:

app/Livewire/Dashboard/Post/Save.php

<?php

namespace App\Livewire\Dashboard\Post;

use App\Livewire\Forms\PostForm;
use Livewire\Attributes\Locked;
use Livewire\Component;

use App\Models\Category;
use App\Models\Post;

class Save extends Component
{

   public $post;
   public PostForm $form;

   #[Locked]
   public $id;

   public function render()
   {
       $categories = Category::get();
       return view('livewire.dashboard.post.save', compact('categories'));
   }

   function mount(?int $id = null)
   {
       if ($id != null) {
           $this->id = $id;
           $this->post = Post::findOrFail($id);
           $this->form->text = $this->post->text;
           $this->form->title = $this->post->title;
           $this->form->category_id = $this->post->category_id;
           $this->form->posted = $this->post->posted;
           $this->form->type = $this->post->type;
           $this->form->description = $this->post->description;
           $this->form->date = $this->post->date;
       }
   }

   function submit(/*$content*/)
   {
       $this->validate();

       if ($this->post) {
           $this->post->update($this->form->all());
           $this->dispatch('updated');
       } else {
           $this->post = Post::create($this->form->all());
           $this->dispatch('created');
       }

       // upload
       if ($this->form->image) {
           $imageName = $this->post->slug . '.' . $this->form->image->getClientOriginalExtension();
           $this->form->image->storeAs('images/post', $imageName, 'public_upload');

           $this->post->update([
               'image' => $imageName
           ]);
       }
   }
}

Y en la vista, las referencias a cada uno de los campos:

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

<div>
***
       <form wire:submit.prevent="submit" class="flex flex-col gap-4">

           <flux:input wire:model="form.title" :label="__('Title')" />
           <flux:input wire:model="form.date" :label="__('Date')" type="date" />
           <flux:textarea wire:model="form.description" :label="__('Description')" />

           <flux:textarea wire:model="form.text" :label="__('Text')" />

           <flux:label>{{ __('Posted') }}</flux:label>
           <flux:select wire:model='form.posted'>
               <option value=""></option>
               <option value="yes">Yes</option>
               <option value="not">Not</option>
           </flux:select>

           <flux:label>{{ __('Type') }}</flux:label>
           <flux:select wire:model='form.type'>
               <option value=""></option>
               <option value="advert">Advert</option>
               <option value="post">Post</option>
               <option value="course">Course</option>
               <option value="movie">Movie</option>
           </flux:select>

           <flux:label>{{ __('Category') }}</flux:label>
           <flux:select wire:model='form.category_id'>
               <option value=""></option>
               @foreach ($categories as $c)
                   <option value="{{ $c->id }}">{{ $c->title }}</option>
               @endforeach
           </flux:select>

           <flux:input wire:model="form.image" type='file' :label="__('Image')" />

           @if ($post && $post->image)
               <img class="w-40 my-3" src="{{ $post->getImageUrl() }}" alt="{{ $post->title }}">
           @endif

           <div>
               <flux:button variant="primary" type="submit">
                   {{ __('Save') }}
               </flux:button>
           </div>
       </form>
   </div>
</div>

Detestaba usar Formularios Largos en Laravel Livewire hasta que descrubrí..  livewire:form

Video thumbnail

Estaba a punto de hacer un video hablando mal sobre Livewire, lo cual puede ser sorprendente si has seguido el resto de mis videos, en los cuales le he tirado un poquito de tierra —de manera humorística, o al menos así me gusta verlo— a lo que es Inertia. Siempre que comparo Livewire con Inertia, le lanzo alguna que otra crítica a Inertia.

Y ahora, curiosamente, le estoy tirando un poquito de tierra a Livewire, porque estoy un poco fastidiado con Laravel últimamente.

Componentes de Livewire y muchos campos no son buenos compañeros

A mí no me gustaba mucho el uso de componentes en Livewire para definir múltiples campos. Obviamente, este es un ejemplo simple, pero nosotros sabemos que cuando estamos trabajando con formularios reales, no son dos campos... pueden ser 10, 15, 20 campos.

Yo, por ejemplo, para el formulario de creación de un post, tengo como 20 campos. Entre contenido, descripción, metadatos, categorías, etiquetas, etc.

Y claro, con componentes individuales por campo, eso se vuelve una locura: la comunicación, el manejo de estado, la validación… todo se complica innecesariamente.

Proceso para crear

Video thumbnail

Vamos a empezar a mostrar los potenciales de los componentes en Livewire; lo primero que vamos a hacer es el componente para crear en nuestros CRUDs, siempre teniendo presente, la comparación con los controladores en Laravel básico.

Implementación mínima para crear un registro:

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

<?php

namespace App\Http\Livewire\Dashboard\Category;

use App\Models\Category;
use Livewire\Component;

class Save extends Component
{
    public $title;
    public $text;

    public function render()
    {
        return view('livewire.dashboard.category.save');
    }

    public function submit()
    {
        Category::create(
            [
                'title' => $this->title,
                'slug' => str($this->title)->slug(),
                'text' => $this->text,
            ]
        );
    }
}

Esto es todo el código que necesitamos para crear una categoría; es muy limpio; apenas una función extra, que puede tener cualquier nombre, que crea una categoría con las propiedades definidas anteriormente; en este caso, no le estamos pasando el contenido e imagen, ya que son opcionales y la idea es mostrar una implementación mínima.

Y su vista:

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

<div>
    <form wire:submit.prevent='submit'>   
        <label for="">Title</label>
        <input type="text" wire:model='title'>

        <label for="">Text</label>
        <input type="text" wire:model='text'>

        <button type="submit">Send</button>
    </form>
</div>

Un formulario básico o típico que no debe de causar mayor asombro, pero con dos cambios a considerar:

  • El wire:submit.prevent que permite interceptar y detener una petición submit por el navegador e invocar un método, un método que no tenemos definido en nuestra vista PHP; este nombre de la función va a invocar a la función que se encuentra en la clase componente de Livewire; es decir, desde una vista del lado del cliente, podemos invocar en base a eventos HTML a una función del lado del servidor sin necesidad de usar ajax, fetch, axios o similares; ya Livewire se encarga de este proceso por nosotros.
  • El wire:model que son similares a los v-model de Vue, pero usando Livewire; y es que, siguiendo el esquema anterior, indicando el nombre de las propiedades de la clase componente, podemos referenciarlos desde aquí, desde una vista.

Todas estas características las tenemos de gratis al emplear el atributo mágico llamado wire, junto con alguna opción de las muchas que podemos usar; finalmente, sí desde el formulario, pasamos algunos datos y damos a enviar.

Validar datos

Video thumbnail

Otro factor importante es la validación de formularios; la misma estructura que manejábamos con Laravel básico, con el manejo de las validaciones en clases apartes o mediante el request, las podemos hacer localmente en el componente de Livewire.

Para validar en Livewire, tenemos que definir una propiedad protegida llamada $rules la cual le tenemos que definir las reglas de validación:

protected $rules = [
     'title' => "required|min:2|max:255",
     'text' => "nullable"
];

Importante notar que, las keys de las reglas tienen que ser las propiedades que estés usando en el wire:model y no las columnas o propiedades del modelo que estes administrando; aparte de esto, tienes que llamar al método validate() para aplicar las validaciones; por lo demás, muestras los posibles errores empleando la directiva error de blade.

Caso práctico

Vamos a modificar la clase componente como:

class Save extends Component
{

    public $title;
    public $text;

    protected $rules = [
        'title' => "required|min:2|max:255",
        'text' => "nullable",
    ];*
***

    public function submit()
    {

        // validate
        $this->validate();

        $this->category = Category::create(
            [
                'title' => $this->title,
                'slug' => str($this->title)->slug(),
                'text' => $this->text,
                ]
            );
***

En el cual puedes ver los cambios indícanos anteriormente; la propiedad $rules y el uso del método validate() para validar los datos.

En el componente de vista, mostramos los errores:

<div>
    <form wire:submit.prevent="submit">

        <label for="">Título</label>

        @error('title')
            {{$message}}
        @enderror

        <input type="text" wire:model="title">

        @error('text')
            {{$message}}
        @enderror

        <label for="">Texto</label>
        <input type="text" wire:model="text">

        <button type="submit">Enviar</button>
    </form>
</div>

Ya con esto, si enviamos un formulario inválido, veremos los errores por pantalla.

Proceso de Crear y Editar (CRUD) 

Video thumbnail

Veremos como crear el proceso CRUD en Livewire.

Las rutas:

Route::group(['prefix' => 'post'],function () {
       Route::get('/', App\Http\Livewire\Dashboard\Post\Index::class)->name("d-post-index");        // listado
       Route::get('/create', App\Http\Livewire\Dashboard\Post\Save::class)->name("d-post-create");  // crear
       Route::get('/edit/{id}', App\Http\Livewire\Dashboard\Post\Save::class)->name("d-post-edit");// edit
});

A continuación, se presenta el código para el apartado de crear y actualizar un post:

app/Http/Livewire/Dashboard/Post/Save.php

<?php

namespace App\Http\Livewire\Dashboard\Post;

use App\Models\Category;
use App\Models\Post;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
use Livewire\WithFileUploads;

class Save extends Component
{
   use WithFileUploads;

   public $title;
   public $date;
   public $text;
   public $description;
   public $posted;
   public $type;
   public $category_id;
   public $image;

   public $post;

   protected $rules = [
       'title' => "required|min:2|max:255",
       'description' => "required|min:2|max:255",
       'date' => "required",
       'type' => "required",
       'category_id' => "required",
       'posted' => "required",
       'text' => "required|min:2|max:5000",
       'image' => "nullable|image|max:1024",
   ];

   public function mount($id = null)
   {
       if ($id != null) {
           $this->post = Post::findOrFail($id);
           $this->title = $this->post->title;
           $this->text = $this->post->text;
           $this->date = $this->post->date;
           $this->description = $this->post->description;
           $this->posted = $this->post->posted;
           $this->type = $this->post->type;
           $this->category_id = $this->post->category_id;
       }
   }

   public function render()
   {
       $categories = Category::get();
       return view('livewire.dashboard.post.save', compact('categories'));
   }

   public function submit()
   {

       // validate

       $this->validate();

       // save

       if ($this->post) {
           $this->post->update([
               'title' => $this->title,
               'text' => $this->text,
               'description' => $this->description,
               'date' => $this->date,
               'posted' => $this->posted,
               'type' => $this->type,
               'category_id' => $this->category_id,
           ]);
           $this->dispatch("updated");
       } else {
           $this->post = Post::create(
               [
                   'title' => $this->title,
                   'slug' => str($this->title)->slug(),
                   'text' => $this->text,
                   'description' => $this->description,
                   'date' => $this->date,
                   'posted' => $this->posted,
                   'type' => $this->type,
                   'category_id' => $this->category_id,
               ]
           );
           $this->dispatch("created");
       }

       // upload
       if ($this->image) {
           $imageName = $this->post->slug . '.' . $this->image->getClientOriginalExtension();
           $this->image->storeAs('images/post', $imageName, 'public_upload');

           $this->post->update([
               'image' => $imageName
           ]);
       }
   }
}

Y su vista, que, como detalle importante, recuerda colocar el campo para las fechas como tipo date en HTML:

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

<div>

   <div class="container">

       <x-action-message on="created">
           <div class="box-action-message">
               {{ __('Created post success') }}
           </div>
       </x-action-message>

       <x-action-message on="updated">
           <div class="box-action-message">
               {{ __('Updated post success') }}
           </div>
       </x-action-message>


       <x-form-section submit='submit'>

           <x-slot name="title">
               {{ __('Post') }}
           </x-slot>

           <x-slot name="description">
               Lorem ipsum dolor sit amet consectetur adipisicing elit. Neque hic voluptates quaerat accusantium a. Est
               voluptate voluptatibus necessitatibus a non iure rerum, nesciunt nisi assumenda quaerat nam incidunt ab.
               Facilis.
           </x-slot>

           @slot('form')

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Title</x-label>
                   <x-input type="text" wire:model.live='title' class="w-full" />
                   @error('title')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Date</x-label>
                   <x-input type="date" wire:model='date' class="w-full" />
                   @error('date')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Description</x-label>
                   <textarea wire:model='description' class="block w-full"></textarea>
                   @error('description')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Text</x-label>
                   <textarea wire:model='text' class="block w-full"></textarea>
                   @error('description')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Posted</x-label>
                   <select class="block w-full" wire:model='posted'>
                       <option value=""></option>
                       <option value="yes">Yes</option>
                       <option value="not">Not</option>
                   </select>
                   @error('posted')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Type</x-label>
                   <select class="block w-full" wire:model='type'>
                       <option value=""></option>
                       <option value="Advert">Advert</option>
                       <option value="post">Post</option>
                       <option value="course">Course</option>
                       <option value="movie">Movie</option>
                   </select>
                   @error('type')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Category</x-label>
                   <select class="block w-full" wire:model='category_id'>
                       <option value=""></option>
                       @foreach ($categories as $c)
                           <option value="{{ $c->id }}">{{ $c->title }}</option>
                       @endforeach
                   </select>
                   @error('category')
                       {{ $message }}
                   @enderror
               </div>

               <div class="col-span-10 sm:col-span-3">
                   <x-label for="">Image</x-label>
                   <x-input type="file" wire:model='image' class="w-full" />
                   @error('image')
                       {{ $message }}
                   @enderror

                   @if ($post && $post->image)
                       <img class="w-40 my-3" src="{{ $post->getImageUrl() }}" alt="{{ $post->title }}">
                   @endif
               </div>
           @endslot

           @slot('actions')
               <x-button type="submit">Send</x-button>
           @endslot
       </x-form-section>
   </div>
</div>

wire:blur Actualiza al momento del tab o perder el foco del campo del formulario

Video thumbnail

Aquí hay otra opción que considero muy interesante que es la de blur:

<flux:input wire:model.blur="title" :label="__('Title')" />

En definitiva es muy similar a la de live esto también lo tuve que como quien dice colocar en en la sección correspondiente en la cual te hablaba sobre los modificadores que podíamos utilizar pero lo ya que lo estoy viendo ahorita lo puedo agregar aquí tampoco aquí rapidito entonces tampoco pasa tanto esto es básicamente el mismo live que tenemos antes recuerda que el temita del live:

<flux:select class="block w-full" wire:model.live='posted'>

Las peticiones se envían en Livewire al servidor cada vez que escribimos.

En local, no tendremos problemas, pero cuando pasa esa producción lo que te comentaba antes secciones anteriores si tuviéramos 1000 usuarios 1000 usuarios escribiendo 10 letras sobre este campo puedes sacar la cuenta obviamente esto va puede ocasionar un problema de rendimiento en el servidor entonces por eso es que como quien dice le invirtieron en Livewire 3, antes funcionaba así ya no funciona así tal cual puedes ver y tal cual te comentaba antes y tal cual puedes deducir pero este es útil por ejemplo para ciertas características de nuestra aplicación 

¿Qué es lo que hace el .blur?

Fíjate que escribimos no hace nada así que pareciera que no funciona pero si aquí pierde el foco o damos un tap a otro campo, se envía la petición, lo cual puede ser útil para determinadas funcionalidades en nuestra aplicación en Laravel Livewire.

Conclusión

Livewire simplifica por completo la creación de formularios: menos JavaScript, más productividad. En mi día a día, crear o editar un registro —como una categoría con imagen— se vuelve tan natural que casi siempre reutilizo el mismo patrón que te mostré aquí.

Si aplicas validación avanzada, cargas de archivo, y una buena organización del componente, puedes construir formularios de nivel profesional sin complicarte.

El siguiente paso, el uso de las propiedades en Laravel Livewire.

Acepto recibir anuncios de interes sobre este Blog.

Veremos como trabajar los procesos de crear y editar en Laravel Livewire mediante componentes que es la fase inicial de cualquier proceso CRUD.

| 👤 Andrés Cruz

🇺🇸 In english