If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur /api/purchases
API endpoint already has validation rules on it. Yay! Look at the interactive docs and hit "Try it out". I'll empty a couple of fields... and set purchaseItems
to an empty array. Hit Execute.
Yea! A 400 error with a really nice structure for the validation error messages. We'll definitely be able to use that in Vue.
If you're a Symfony user, these validation rules are set as normal constraints inside of the Purchase
class, like @Assert\NotBlank
.
Thinking about how to handle this in Vue, one nice thing is that if the status code is 400, then we know we have some sort of validation error response. If the AJAX call fails with some other status code, that would be an unexpected error. And because this is a super-sensitive checkout form, I want to handle both cases: normal validation errors and unexpected errors.
Let's start by printing a message up here if something weird happens on submit. In the CheckoutForm
component, find data
and add a new key: serverError
set to false.
... lines 1 - 62 | |
<script> | |
... lines 64 - 67 | |
export default { | |
name: 'CheckoutForm', | |
... lines 70 - 79 | |
data() { | |
return { | |
... lines 82 - 91 | |
serverError: false, | |
}; | |
}, | |
... lines 95 - 133 | |
}; | |
</script> |
Then, head down to onSubmit
and add this.serverError = false
at the beginning. If the form is submitted multiple times, this will re-set this back to false each time.
... lines 1 - 62 | |
<script> | |
... lines 64 - 67 | |
export default { | |
name: 'CheckoutForm', | |
... lines 70 - 79 | |
data() { | |
return { | |
... lines 82 - 91 | |
serverError: false, | |
}; | |
}, | |
methods: { | |
... lines 96 - 109 | |
async onSubmit() { | |
this.loading = true; | |
this.serverError = false; | |
... lines 113 - 131 | |
}, | |
}, | |
}; | |
</script> |
Down in catch
, let's get to work. First, grab the response with some fancy de-structuring: const { response } = error
. Then if response.status
- that's the property that holds the integer status code - does not equal 400, we know something weird happened. So let's say this.serverError = true
. Else, we have a normal validation error situation... which we'll handle in a few minutes. For now, console.error(response.data)
.
... lines 1 - 62 | |
<script> | |
... lines 64 - 67 | |
export default { | |
name: 'CheckoutForm', | |
... lines 70 - 79 | |
data() { | |
return { | |
... lines 82 - 91 | |
serverError: false, | |
}; | |
}, | |
methods: { | |
... lines 96 - 109 | |
async onSubmit() { | |
this.loading = true; | |
this.serverError = false; | |
try { | |
... lines 115 - 120 | |
} catch (error) { | |
const { response } = error; | |
if (response.status !== 400) { | |
this.serverError = true; | |
} else { | |
console.error(response.data); | |
} | |
} finally { | |
this.loading = false; | |
} | |
}, | |
}, | |
}; | |
</script> |
Up in the template, let's use this new serverError
to render a message right inside the form. Add a <div>
with v-show="serverError"
, some classes for styling and a message.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form @submit.prevent="onSubmit"> | |
<div | |
v-show="serverError" | |
class="alert alert-danger" | |
> | |
Well this is embarrassing ... something went wrong! | |
Please try again! | |
</div> | |
... lines 12 - 65 | |
</form> | |
</div> | |
</div> | |
</template> | |
... lines 70 - 144 |
So how can we test this... when our API is so awesome that nothing unexpected ever happens? Great question! We'll just... break the URL! In checkout-service.js
, change the URL to something that will 404.
Let's do this! Go to the checkout form and submit. Perfecto! The error message even disappears momentarily each time we submit.
Let's go back and fix that URL... so I don't forget and deploy this to production.
Before we handle validation failures, I think we should also disable the submit button so the user can't accidentally hit it multiple times. Our products are so exciting that we've had this problem in the past.
Thanks to our loading
data... it's pretty easy. Down on the button, add :disabled="loading"
.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form @submit.prevent="onSubmit"> | |
... lines 5 - 53 | |
<div class="form-row justify-content-end align-items-center"> | |
... lines 55 - 56 | |
<div class="col-auto"> | |
<button | |
... lines 59 - 60 | |
:disabled="loading" | |
> | |
Order! | |
</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
</template> | |
... lines 71 - 145 |
We can immediately see the results: Bootstrap even has some styling to make it look disabled.
Next: let's handle the most important part: validation errors.
"Houston: no signs of life"
Start the conversation!
// package.json
{
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.15.1", // 5.15.1
"@symfony/webpack-encore": "^0.30.0", // 0.30.2
"axios": "^0.19.2", // 0.19.2
"bootstrap": "^4.4.1", // 4.5.3
"core-js": "^3.0.0", // 3.6.5
"eslint": "^6.7.2", // 6.8.0
"eslint-config-airbnb-base": "^14.0.0", // 14.2.0
"eslint-plugin-import": "^2.19.1", // 2.22.1
"eslint-plugin-vue": "^6.0.1", // 6.2.2
"regenerator-runtime": "^0.13.2", // 0.13.7
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^8.0.0", // 8.0.2
"vue": "^2.6.11", // 2.6.12
"vue-loader": "^15.9.1", // 15.9.4
"vue-template-compiler": "^2.6.11", // 2.6.12
"webpack-notifier": "^1.6.0" // 1.8.0
}
}