---
title: "Livewire 3 Forms"
---

# Livewire 3 Forms

[Annie Ruygt](https://annieruygtillustration.com/)

Fly.io can build and run your Laravel apps *globally* . Deploy your [Laravel application](https://docs.fly.io/laravel) on Fly.io, you’ll be up and running in minutes!

Forms are why I use Laravel. Anything that abstracts away the tedium of form security, validation, user-messaging (“hey this is required”), and saving state is my favorite thing. This is, in fact, a huge part of what keeps me using Laravel over any other framework.

Livewire has always made Forms easy to work with. In Livewire 3, it gets even better with a handy abstraction (or perhaps we’ll call this an “extraction”).

Similar to how Laravel has [custom Request objects](https://laravel.com/docs/10.x/validation#form-request-validation), [InertiaJS has a form helper](https://inertiajs.com/forms), and Spatie has (had?) that amazing-for-its-time [frontend form validation](https://github.com/spatie/form-backend-validation), Livewire 3 now has a [form abstraction](https://livewire.laravel.com/docs/forms).

This cleans up your Component code a ton - form input validation / handling in any web app is so routine, so rote, so *tediously* omnipresent that it’s nice to have it be easy AND tucked out of the way - giving the more interesting code the chance to stick in our brain in our flow state.

## Forms

Instead of jamming form “stuff” into our Livewire component, we can create a new Form object:

```
php artisan livewire:form SnackRequestForm
```

We can take that form object and add the fields / validation rules:

```
namespace App\Livewire\Forms;

use Livewire\Attributes\Rule;
use Livewire\Form;

class SnackRequestForm extends Form
{
    #[Rule('required|in:sweet,salty,hot,cold,disgusting')]
    public $type = '';

    #[Rule('required|in:asap,morning,afternoon')]
    public $time = '';
}
```

Then, in our component, we can use that form:

```
namespace App\Livewire;

use Livewire\Component;
use App\Models\SnackRequest;
use App\Livewire\Forms\SnackRequestForm;

class SnackRequest extends Component
{
    public SnackRequestForm $form;

    public function save()
    {
        SnackRequest::create(
            $this->form->all()
        );

        return $this->redirect('/requests');
    }

    public function render()
    {
        return view('livewire.request-snack');
    }
}
```

Finally, your inputs in the HTML form would reference the form itself, e.g. your input list of possible snack types would have `wire:model="form.type"` instead of `wire:model="type"`. The same goes for validation errors, e.g. you can use `@error('form.type') {{ $message }} @enderror` if an invalid `type` was passed.

```
<form wire:submit="save">
    <select wire:model="form.type">
        <option value="disgusting">Disgusting</option>
        <!-- and so on -->
    </select>
    <div>
        @error('form.type') <span class="error">{{ $message }}</span> @enderror
    </div>
    <!-- and so on -->
    <button type="submit">Save</button>
</form>
```

Now the more boring form stuff is tucked away.

## Validation

Our form right now won’t actually do any validation - we need to use one of 2 options to get it validating.

The first way is to just call `$this->validate()` in our `save()` method:

```
public function save()
{
    $this->validate();

    SnackRequest::create(
        $this->form->all()
    );

    return $this->redirect('/requests');
}
```

This method will validate only on form submit.

However, we could instead make our [model bindings “live”](https://livewire.laravel.com/docs/forms#live-updating-fields). Adding this makes it so updating field values sends a request across the wire (thus updating the field value “live”), which gives the backend a chance to validate it.

This works well with select lists in a simple form like ours. To use it, we append `.live` to `wire:model` in our blade template:

```
<form wire:submit="save">
-    <select wire:model="form.type">  
+    <select wire:model.live="form.type">
        <option value="disgusting">Disgusting</option>
        <!-- and so on -->
    </select>
    <div>
        @error('form.type') <span class="error">{{ $message }}</span> @enderror
    </div>
    <!-- and so on -->
    <button type="submit">Save</button>
</form>
```

Note the diff there where we append `.live` so it becomes `wire:model.live="foo"`.

## Further Refactoring

Since the Form object is just a class, you could add methods to it. Perhaps you could have the form save itself?

The component:

```
public function save()
{
+    $this->form->submit();
-    SnackRequest::create(
-        $this->form->all()
-    );

    return $this->redirect('/requests');
}
```

And then in the form object:

```
public function submit()
{
    SnackRequest::create(
        $this->all()
    );
}
```

That’s (potentially) tidier, depending on what data/logic your form needs when performing needed actions. You’ll need to decide if you like this “pattern” or not.

## Fly.io ❤️ Laravel

Fly your servers close to your users—and marvel at the speed of close proximity. Deploy globally on Fly in minutes!

[Deploy your Laravel app! →](https://docs.fly.io/laravel)

## How’s It Work?

As I looked into the new form objects, I realized it wasn’t obvious when validation happened!

You can call `$this->validate()` anytime within the component. However, validation also happens automagically whenever a mutation happens to one of the public properties (e.g. when you `wire:model.live="form.type"` and make a change to that input field).

We needed to add the `.live` modifier to trigger the validation, however, as otherwise Livewire doesn’t see the value of the field changing on the backend and thus wouldn’t validate it.

Digging a bit deeper - the form objects all extend [this empty (but nicely namespaced) class](https://github.com/livewire/livewire/blob/main/src/Form.php) which in turn extends [this internal base class](https://github.com/livewire/livewire/blob/main/src/Features/SupportFormObjects/Form.php).

The new form objects collects older-style, component-defined validation rules (returned via method `rules()` on a component) and finds properties with attribute based validation defined within the form object. These get merged together, so validation happens smoothly and is backwards compatible from Livewire 2.
