In recent web applications we have many forms with the same submission structure:
isSubmitting
variableYup
)isSubmitting
back to the false setting and display validationErrors
I have tried doing some operations using the composition api in vue 3.
Login.vue
<template> <div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8"> <div class="sm:mx-auto sm:w-full sm:max-w-md"> <h1 class="text-3xl text-center text-gray-900">{{ t('sign_in_account', 1) }}</h1> </div> <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> <form @submit.prevent="handleSubmit"> <fieldset :disabled="isSubmitting" class="space-y-6"> <MessageBox v-if="errors.general" :title="errors.general" :messages="errors.messages" /> <Input :label="t('email', 1)" type="text" id="email" v-model="user.email" :error="errors.email" /> <Password :label="t('password', 1)" type="password" id="password" v-model="user.password" :error="errors.password" /> <div class="text-sm text-right"> <router-link class="font-medium text-indigo-600 hover:text-indigo-500" :to="forgotPassword">{{ t('forgot_password', 1) }}</router-link> </div> <SubmitButton class="w-full" :label="t('sign_in', 1)" :submittingLabel="t('sign_in_loader', 1)" :isSubmitting="isSubmitting" /> </fieldset> </form> </div> </div> </div> </template> <script> import { ref } from 'vue'; import { useStore } from 'vuex'; import { useI18n } from 'vue-i18n'; import useForm from '@/use/useForm'; import { validateEmail, LoginValidationSchema } from '@/utils/validators'; export default { setup() { const store = useStore(); const { t } = useI18n({ useScope: 'global' }); const user = ref({ email: '', password: '', }); const { handleSubmit, isSubmitting, errors } = useForm(user, LoginValidationSchema, handleLogin); async function handleLogin(values) { try { return await store.dispatch('auth/login', values); } catch (error) { if (error.response) { console.log(error.reponse); if (error.response.status == 422) { errors.value = { general: `${t('unable_to_login', 1)}<br /> ${t('fix_and_retry', 1)}`, messages: Object.values(error.response.data.errors).flat(), }; } else if (error.response.data.message) { errors.value = { general: error.response.data.message, }; } else { errors.value = { general: `${t('unknown_error', 1)}<br /> ${t('please_try_agin', 1)}`, }; } } else if (error.request) { console.log(error.request); errors.value = { general: `${t('unknown_error', 1)}<br /> ${t('please_try_agin', 1)}`, }; } else { errors.value = { general: `${t('unknown_error', 1)}<br /> ${t('please_try_agin', 1)}`, }; } return; } } return { t, user, handleSubmit, isSubmitting, errors }; }, computed: { forgotPassword() { return validateEmail(this.user.email) ? { name: 'forgotPassword', query: { email: this.user.email } } : { name: 'forgotPassword' }; }, }, }; </script>
useForm.js
import { ref, watch } from 'vue'; export default function useForm(initialValues, validationSchema, callback) { let values = ref(initialValues); let isSubmitting = ref(false); let errors = ref({}); async function handleSubmit() { try { errors.value = {}; await validationSchema.validate(values.value, { abortEarly: false }); isSubmitting.value = true; } catch (err) { console.log('In the catch'); isSubmitting.value = false; err.inner.forEach((error) => { errors.value = { ...errors.value, [error.path]: error.message }; }); } } watch(isSubmitting, () => { if (Object.keys(errors.value).length === 0 && isSubmitting.value) { callback(values); isSubmitting.value = false; } else { isSubmitting.value = false; } }); return { handleSubmit, isSubmitting, errors }; }
This works to some extent, but I'm missing two things. In useForm
, I want to wait until the callback completes (success or failure) before setting isSubmitting
back to false. Is commitment a good way to do this? Is there a better way? Secondly, I want a reusable way to handle errors in Login.vue
. Any suggestions on how to deal with this issue?
Regarding your first question - the
try..catch
statement has a third statement namedfinally
which is always inside thetry
statement Executeafter the block is completed.
To answer your second question - Promises are a good way to handle asynchronous logic, including situations where the API you're sending the request to returns an error response, and then you can decide how to handle the user experience in that case.
I'm not quite sure what you mean by handling errors in
Login.vue
in a reusable way, but maybe you could simply pass an empty array prop calledformErrors
ofuseForm
and have youruseForm.js
emit anupdate:modelValue
event to get two-way binding.