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

Processing Form: Loading Indicator

If the form takes longer to submit, then we need to show some kind of loading indicator and disable the submit button so the user wouldn't hit it twice.

loading indicator


It's pretty easy to achieve. In the posts Composable, we need a variable isLoading which will be true or false. When the button is clicked we just set it as true.

Also, we will reset the validation errors from the previous lesson. And if there are any errors, we need to set isLoading to false so that we could submit the form again.

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 isLoading = ref(false)
// ...
const storePost = async (post) => {
if (isLoading.value) return;
 
isLoading.value = true
validationErrors.value = {}
 
axios.post('/api/posts', post)
.then(response => {
router.push({ name: 'posts.index' })
})
.catch(error => {
if (error.response?.data) {
validationErrors.value = error.response.data.errors
isLoading.value = false
}
})
}
 
return { posts, getPosts, storePost, validationErrors }
return { posts, getPosts, storePost, validationErrors, isLoading }
}

And in the PostsCreate Vue component for the button we need to do a couple of things:

  • Bind disabled to the isLoading variable.
  • Using the v-show directive show the loading spinner.
  • And last use v-if: if isLoading is true then show the text Processing..., otherwise using v-else show the Save text.

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

<template>
<form @submit.prevent="storePost(post)">
// ...
<!-- Buttons -->
<div class="mt-4">
<button class="rounded-md bg-indigo-600 px-3 py-2 text-sm uppercase text-white">Save</button>
<button :disabled="isLoading" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm uppercase text-white disabled:opacity-75 disabled:cursor-not-allowed">
<span v-show="isLoading" class="inline-block animate-spin w-4 h-4 mr-2 border-t-2 border-t-white border-r-2 border-r-white border-b-2 border-b-white border-l-2 border-l-blue-600 rounded-full"></span>
<span v-if="isLoading">Processing...</span>
<span v-else>Save</span>
</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()
const { storePost, validationErrors, isLoading } = usePosts()
 
onMounted(() => {
getCategories()
})
</script>

That's it. This is how easy it is to add a loading indicator.

loading indicator