This course is archived!
While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).
Server Validation & fetch Failing
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeEarlier, we talked about the three types of validation. First, HTML5 validation with things like the required
attribute. It's dead-simple to setup, but limited. Second, custom client-side validation, which we added because we wanted to make sure the users enter a positive quantity. And third, of course, the one type of validation you must have: server-side validation.
Our Server Side Validation
Look at the RepLog
entity. We already have a few important validation constraints: reps
cannot be blank and needs to be a positive number, and the item
also cannot be blank.
Thanks to HTML5 validation & client-side validation, we are already preventing these bad values from even being submitted to the server. And, of course, if some annoying hacker wants to send bad values to the API, sure, they totally could. But then our server-side validation would be there to tell them to bugger off.
However, a lot of time, I either skip client-side validation entirely, or just add it for a few things, but not everything. And, in that case, if an API request fails because of failed server-side validation, our React app needs to read those errors from the server and tell the user.
Check out RepLogController
. We're using the form system, but that's not important. Nope, the really important thing is that we, somehow, get a RepLog
object that's populated with data, and run it through the validation system. The form does this for us. But if you were manually setting up the object or using the serializer to deserialize, you could pass the object directly to the validation system to get back a collection of errors.
In this application, I added a shortcut method called getErrorsFromForm
, which lives in BaseController
. This recursively loops over my errors to create a big array of errors, where the key is the name of the field. This is what's returned from our API.
When you use the form system, there is one other way to add validation, which is often forgotten: on the form itself: RepLogType
. The ChoiceType
is normally used to render a select
element where choices
is where you define the valid options. When used in an API, if we submit a value that is not in choices, the form will fail validation.
Sending a Bad "Item"
For testing purposes, let's purposely make this easy to do. In RepLogCreator
, find the itemOptions
array: these items match what's configured inside the form. Add a fake one: invalid_item
with text Dark Matter
.
// ... lines 1 - 4 | |
constructor(props) { | |
// ... lines 6 - 14 | |
this.itemOptions = [ | |
// ... lines 16 - 19 | |
{ id: 'invalid_item', text: 'Dark Matter' } | |
]; | |
// ... lines 22 - 23 | |
} | |
// ... lines 25 - 101 |
The server will not like this value. Let's try it anyways! Move over, select "Dark Matter", 10 and... ah! Run! Woh!
Ok, two things. First, you can see the request failed: 400 bad request. Great! Our server-side validation is working and you can see the message in the response. But, second, React exploded in a crazy way! Something about how each child in an array should have a unique "key" prop, from RepLogList
.
We know that error... but why is it suddenly happening?
Fetch is Afraid of Failure
There's one simple explanation and it's hiding in rep_log_api.js
. If you used jQuery's AJAX function, you might remember that if the server returns an error status code, a failure callback is executed instead of your .then()
, success callback. That makes sense: the request failed!
But... fetch()
does not do this. Even if the server sends back a 400 or 500 error... fetch thinks:
We did it! We made a request! Yaaay! Let's execute the
.then()
success callbacks!
Thanks to that, our app parsed the JSON, thought it contained a rep log, tried to add it to state, and things went bananas.
Making fetch Fail
This behavior... isn't great. So, I like to fix it: I'll paste in a new function called checkStatus()
. If we call this function and the status code is not 200 or 300, it creates an Error
object, puts the response onto it, and throws it. By the way, you could change this logic to also throw an error for 300-level status codes, that's actually how jQuery works.
// ... lines 1 - 12 | |
function checkStatus(response) { | |
if (response.status >= 200 && response.status < 400) { | |
return response; | |
} | |
const error = new Error(response.statusText); | |
error.response = response; | |
throw error | |
} | |
// ... lines 23 - 48 |
To use this, back up in fetchJson()
, add this handler: .then(checkStatus)
.
function fetchJson(url, options) { | |
return fetch(url, Object.assign({ | |
// ... line 3 | |
}, options)) | |
.then(checkStatus) | |
.then(response => { | |
// ... lines 7 - 9 | |
}); | |
} | |
// ... lines 12 - 48 |
Let's try it! Refresh, select our bad item, a number and... yes! This is a much more obvious message:
Uncaught (in promise) Error: Bad Request at
checkStatus
Now that fetch
is behaving better, let's use this error response to add a message for the user.
Can't wait for the next episode. Really enjoing this so far!