Inline form validation with InertiaJS, Vue, and Laravel

In this post, we’ll go over how we can handle inline form validation with InertiaJS, Vue, and Laravel. As an example, we’ll take a basic todo list where we can edit each item using an inline form.

All we have above is a v-for that loops through a todos array and displays the form whenever we click on the edit link – by filling a this.form with the todo’s contents and matching the todo.id within the iteration with the form.id.

The problem we’re trying to solve is connecting the returned validation errors with the exact todo item that triggered them.

One solution is to use Laravel’s named error bags – it allows us to set a unique name that we can later on use to identify the todo that should display the validation messages.

Name your error bags

The name should be unique. A combination of a string, say “todo” and the id should suffice.

If you’re using inline validation you can set a unique name like so:

$request->validateWithBag("todo.{$todo->id}", [
    'description' => ['required']
])

For request objects, we need to set the errorBag property, but you cannot do it directly because expressions are not allowed as field default values.

However, there is a prepareForValidation() method we can tap into:

<?php

class TodoUpdateRequest extends FormRequest 
{
    // this won't work
    protected $errorBag = "todo.{$this->route('todo')->id}";

    // this will
    public function prepareForValidation()
    {
        $this->errorBag = "todo.{$this->route('todo')->id}";
    }
}

Share all error bags with Inertia

You need to make sure you are sharing all the error bags with Inertia, not just the default one. Add this in your AppServiceProvider@register :

// AppServiceProvider
Inertia::share([
    'errors' => function () {
        if ($errors = session('errors')) {
            $bags = [];

            foreach ($errors->getBags() as $name => $bag) {
                $bags[$name] = $bag->getMessages();
            }

            return (object)$bags;
        }

        return (object)[];
    }
])

Create a Vue mixin

Once you are using uniquely named error bags and they are all shared with inertia, you can then create a mixin with an error method so you can use it everywhere in your application.

The error method will take two arguments: field and errorBag, and it will check whether we have an error bag with that name, and if we do, does that bag contain a validation message for our passed in field.

// app.js

Vue.mixin({
  methods: {
    error(field, errorBag = 'default') {
      if(!this.$page.errors.hasOwnProperty(errorBag)) {
        return null;
      }

      if (this.$page.errors[errorBag].hasOwnProperty(field)) {
        return this.$page.errors[errorBag][field][0];
      }

      return null;
    }
  }
})

Using the error method:

<div class="form-group">
  <input type="text" v-model="form.description">
  <span class="error" v-if="error('description', `todo.${form.id}`)">
    {{ error('description', `todo.${form.id}`) }}
  </span>
</div>

Clear the error bags

One last thing you need to make sure is that you clear your error bags whenever you toggle an item. To do that, you need to delete the correct error key before toggling the form.

edit(todo) {
  delete this.$page.errors[`todo.${todo.id}`];
  this.form = todo;
}

Aaaannnd, that’s it. If it was in any way confusing, there’s also a video version here. And if that doesn’t cut it, leave a comment, and I’ll help you out.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.