Genial! - Clase/Objeto formulario en Laravel Livewire, simplifica tus Componentes

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

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.

Acepto recibir anuncios de interes sobre este Blog.

Vamos a conocer como podemos simplificar el manejo del formulario creando una clase formulario

- Andrés Cruz

In english