Back to Course |
Vue.js 3 + Laravel 11 + Vite: SPA CRUD

Validation Errors: Catch and Show Them

Now, when we submit an empty form, the back-end API returns the 422 status code with errors, but there are no visible error messages for the user. Let's fix this.

validation errors


First, in the posts composable, we need to add a new object variable where all the errors will be saved, let's call it validationErrors and fill it inside the .catch function if there are any errors.

resources/js/composables/posts.js:

import { ref } from 'vue'
import { useRouter } from 'vue-router'
 
export default function usePosts() {
const posts = ref({})
const router = useRouter()
const validationErrors = ref({})
// ...
const storePost = async (post) => {
axios.post('/api/posts', post)
.then(response => {
router.push({ name: 'posts.index' })
})
.catch(error => {
if (error.response?.data) {
validationErrors.value = error.response.data.errors
}
})
}
 
return { posts, getPosts, storePost }
return { posts, getPosts, storePost, validationErrors }
}

Next, in the PostsCreate Vue component, we need to get the validationErrors variable and show errors for each input.

resources/js/components/Posts/Create.vue:

<template>
<form @submit.prevent="storePost(post)">
<!-- Title -->
<div>
<label for="post-title" class="block text-sm font-medium text-gray-700">
Title
</label>
<input v-model="post.title" id="post-title" type="text" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.title">
{{ message }}
</div>
</div>
</div>
 
<!-- Content -->
<div class="mt-4">
<label for="post-content" class="block text-sm font-medium text-gray-700">
Content
</label>
<textarea v-model="post.content" id="post-content" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"></textarea>
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.content">
{{ message }}
</div>
</div>
</div>
 
<!-- Category -->
<div class="mt-4">
<label for="post-category" class="block text-sm font-medium text-gray-700">
Category
</label>
<select v-model="post.category_id" id="post-category" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<option value="" selected>-- Choose category --</option>
<option v-for="category in categories" :value="category.id">
{{ category.name }}
</option>
</select>
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.category_id">
{{ message }}
</div>
</div>
</div>
 
<!-- Buttons -->
<div class="mt-4">
<button class="rounded-md bg-indigo-600 px-3 py-2 text-sm uppercase text-white">Save</button>
</div>
</form>
</template>
 
<script setup>
import { onMounted, reactive } from 'vue';
import useCategories from '@/composables/categories';
import usePosts from '@/composables/posts';
 
const post = reactive({
title: '',
content: '',
category_id: ''
})
 
const { categories, getCategories } = useCategories()
const { storePost, validationErrors } = usePosts()
 
onMounted(() => {
getCategories()
})
</script>

If you submit an empty form, the errors would be shown.

validation errors with category id

But for the category, it shows category_id. Wouldn't it be better to just have it named "category" in a human-friendly way? We can do that on the back-end in Laravel, by adding a method attributes in the StorePostRequest.

app/Http/Requests/StorePostRequest.php:

class StorePostRequest extends FormRequest
{
// ...
public function attributes(): array
{
return ['category_id' => 'category'];
}
}

Now it is better, right?

validation errors