Form Validation

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Let's handle the really important case: when the POST request fails with validation errors. Look at the data from the response down in the console log. The data has this really nice violations key... and then each violation has a propertyPath that tells us exactly which field it should be attached to.

Love that! Inside of the checkout form component, we already built this validationErrors object, which is... already being used by each field to figure out whether that field is invalid and what error message to show. So... basically, all we need to do is read violations and map each one to the validationErrors data. We planned ahead and it rocks.

Mapping the API Errors into the Form

Head down to onSubmit(). The first thing we need to do is, at the beginning, add this.validationErrors = an empty object so that any errors from a previous submit are cleared.

... lines 1 - 71
<script>
... lines 73 - 76
export default {
name: 'CheckoutForm',
... lines 79 - 103
methods: {
... lines 105 - 118
async onSubmit() {
... lines 120 - 121
this.validationErrors = {};
... lines 123 - 143
},
},
};
</script>

This is actually going to cause a reactivity problem... a problem where things don't re-render at the right time. Don't worry about that yet: we'll talk about it deeply in a few minutes.

Down in else, say response.data.violations - that will be an array - .forEach() and pass that a callback with a violation argument. The logic we need inside is pretty simple: this.validationErrors[ violation.propertyPath because each violation has that key... and, as you can see in the docs, it will be equal to the, sort of "id", we've been using for each field. Set this to violation.message.

... lines 1 - 71
<script>
... lines 73 - 76
export default {
name: 'CheckoutForm',
... lines 79 - 103
methods: {
... lines 105 - 118
async onSubmit() {
... lines 120 - 123
try {
... lines 125 - 130
} catch (error) {
... lines 132 - 133
if (response.status !== 400) {
this.serverError = true;
} else {
response.data.violations.forEach((violation) => {
this.validationErrors[violation.propertyPath] = violation.message;
});
}
} finally {
this.loading = false;
}
},
},
};
</script>

That... should do it! Move over, get to the checkout form and submit it empty. Woh! That's gorgeous!

Finishing Checkout Success

Before we talk about client-side validation, I think we should finish onSubmit() by handling the happy case: what happens when the form is valid and a new Purchase was created in the API.

Open src/Controller/CheckoutController.php. I've already created a really simple "order confirmation" page. All we need to do is get the id of the new Purchase that was just created by the API and then redirect to /confirmation/{id} that id.

... lines 1 - 10
class CheckoutController extends AbstractController
{
... lines 13 - 20
/**
* @route("/confirmation/{id}", name="app_confirmation")
*/
public function confirmation(Purchase $purchase): Response
{
... lines 26 - 39
}
}

There's nothing really fancy about this. Back in Vue, inside of the successful side of things - the try - add window.location = and then we can use fancy ticks to say /confirmation/, ${} then response.data.id.

... lines 1 - 71
<script>
... lines 73 - 77
export default {
name: 'CheckoutForm',
... lines 80 - 104
methods: {
... lines 106 - 119
async onSubmit() {
... lines 121 - 124
try {
... lines 126 - 132
window.location = `/confirmation/${response.data.id}`;
} catch (error) {
... lines 135 - 143
} finally {
this.loading = false;
}
},
},
};
</script>

This is not @id, it's actually id. If you look at the API docs, I don't always return an id field on my resource... since every resource already has an @id. But I did in this case because it made my life easier.

Oh, and there's one other function I'm going to call. In cart-services.js, search for clearCart. This makes an AJAX call that tells the API that the cart should now be deleted and reset. That could happen automatically on the server when we create a Purchase, but since it doesn't in our API, we'll do it in Vue. Add await clearCart() and hit tab so PhpStorm adds the import for us.

... lines 1 - 71
<script>
... lines 73 - 75
import { clearCart } from '@/services/cart-service';
export default {
name: 'CheckoutForm',
... lines 80 - 104
methods: {
... lines 106 - 119
async onSubmit() {
... lines 121 - 124
try {
... lines 126 - 130
await clearCart();
window.location = `/confirmation/${response.data.id}`;
} catch (error) {
... lines 135 - 143
} finally {
this.loading = false;
}
},
},
};
</script>

So: clear the cart and then redirect to the confirmation page.

Let's try it! Go find the check out form, fill in some real data... this is - I kid you not - the real address of one of my friends - then some fake data and... submit!

It worked! The user now sees a very cutting-edge message about how they can mail us a check or money order to receive their products in a short three to six months. Oh, and also, the shopping cart on top shows zero: the cart was reset.

Before we go find our checkbook and a stamp, we're not quite done yet. Next, let's explore how we could also add client-side validation.

Leave a comment!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// 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
    }
}