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.
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 theisLoading
variable. - Using the
v-show
directive show the loading spinner. - And last use
v-if
: ifisLoading
istrue
then show the textProcessing...
, otherwise usingv-else
show theSave
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.
Really useful. Now I am wondering in the composable way the best approach for dealing with same logic repeated all over the place. That is, clearly the 'isLoading' and 'validaitonErrors' will be needed on each CRUD. So, should a generic frontend Javascript interface should be coded or considering consumption of third party package?
I have a tip for this. Use FormKit it can show errormrssages from the backend and also validate before submitting.
Can anyone answer why there isnt the await keyword after async in storePost? Was it overlooked or is it good practice to add it?