Server-Side Form Validation and Error Handling
00:00 Let's open up the editor route and our action is where server code runs. So the user cannot circumvent this no matter what they do, this server code is the code that they're calling and so it will run. So we can do some validation here. So let's create an errors object and then we can say
00:17 if the title is equal to an empty string, then we know that they did not submit the title. So we can say errors.title. Now here Copilot is saying, hey, why don't you just set errors.title to this string. But it's pretty common to actually have multiple errors per field. So what I'm going to do is turn that into an array
00:35 and we're going to push onto that error. So let's clean this up a little bit. We'll say title and content on our errors. There's actually a pretty common case where sometimes you've got multiple fields that are in error, or maybe the entire form is an error, like you're not even authorized to submit this form.
00:53 What are you doing? So what I like to do as just a regular convention, is I say form errors and that's going to be an array. Then we have field errors and that will be an object. So here we'll say errors.field, errors.title.push.
01:11 This is complaining because it doesn't know what type of this is. So we can say as array of string on all of these and now we are set. Okay, great. If we take a look at the type there, that's exactly what we're hoping for. We can get auto-complete and all that for that.
01:28 Okay, so if the title is an empty string, it's required. Then we can say if the title.length is greater than 100, then we can say title must be 100 characters or less. Now, we have a magic number here.
01:45 Magic numbers are basically like a hard-coded number that doesn't communicate really its intent. It's pretty clear to us looking at this that 100 is fine. But the other problem with magic numbers is keeping them in sync with other places that they need to be. So what we're going to do is I'm going to extract this to
02:03 a variable that we can use that I'm just simply going to call title.max.length. Then we can come up here and we can say title.max.length is 100, and we can do a content.max.length of 10,000. Let's do the same thing there,
02:21 content.max.length. Awesome. Then we can use title.max.length, and we can even use title.max.length in here as well if we want. That works. Awesome. Then we do the same thing for the content.
02:38 Content is required, and if it's greater than the max.length, then we'll do that. Okay, so we've done some validation, but we're not sending those errors back to the user. We need to show those errors and definitely not update the note. So we need to return early or skip updating that note. We don't want to redirect either.
02:56 We want to make sure that we send those errors back so they can finish submitting the form. So we're going to send back some JSON. So to determine whether we have errors, we're going to say has errors, and Copilot could probably fix this for us. Here we go. So we've got form errors, length is greater than zero. So if there are any form errors,
03:14 and then or take all the values of the field errors, and if some of those have a length of greater than zero, then we know that we've got an error there too. That works just fine for me. Thanks, Copilot. So if it has errors, then we're going to return JSON of those errors. I like to use an object for this.
03:33 So I'm going to put that inside of an object, and we can say the status is 400. So the submission was incorrect. Status of 400 is basically like, if you try this again, same thing's going to happen. So don't try it again. You got to change the way that you're submitting that. So we'll save all that, and then actually we can come in here,
03:52 we can try that, and we're going to get the police fill out this field. So what we're going to do is just to test this out. We're going to say no validate. We're going to say browser. I know that we've got these attributes, but we're going to ignore those for now. It's still important to keep those attributes, even if you do no validate because of the screen reader and other implications there. So we're going to leave those on there,
04:12 but add no validate so that we can test out this server interaction, and then we're going to have our own custom validation anyway. So with that, we hit Submit. Here, let's try refreshing and make sure that it's happening. There we go. Great. So we're not getting any validation showing up,
04:31 but you'll notice that I hit Submit and it's not actually redirecting the user. It's not working. If we open up our DevTools, go to the Network tab, and then we try this submission again, we're going to see that 400 status response. So we see the title is empty. If we look at the preview of the response,
04:50 we're going to see field errors, title is required. Perfect. That's exactly what we're looking for. So the next part of this is the client-side to make this actually work and feel nice. So we're going to grab that error list component and stick it right here,
05:06 and then we need to get that action data. So that data that we're sending back, this is going to come from the UseActionData hook. So ActionData equals UseActionData, we'll say type of action, and that's going to come from RemixRunReact. Then we're going to get the field errors.
05:26 So we'll have field errors is going to come from the action data. You'll notice the Elvis operator right there, that's because the action data could potentially be undefined. It will be undefined when the user first appears on the page. They haven't submitted an action, so it will be undefined in that case.
05:45 If it is defined, we'll grab the errors property and then the field errors. If that doesn't exist, then we'll fall back to null. Then we'll have our form errors, and we'll say ActionData errors form errors or fall back to null. Awesome. Then from there,
06:04 we can simply add the errors for each one of these. So I've actually gave you an example so that it can be nicely formatted, so we'll just use that. We've got a div with the class name and all that, and then we have our errors for the title. Then we'll do the same thing for our content.
06:24 Then finally, we do the same thing for the form errors, which will just be form errors. There we go. Now, if I try to submit, we see we've got that there now. We hit Submit. Titles required, and oh-oh, titles required. You got to watch out for those copy-paste errors.
06:44 So let's try that again and get rid of that. Submit. It's working awesome. Then if we do too many characters, we actually can't because the browser is not going to let us, so we're going to cheat and inspect element and increase the max length because we just want to test this out. Add more characters and then hit Submit.
07:03 Title must be 100 characters or less. I'm going to assume that the content is going to work just the same. We don't actually have any errors that are going to show up for the form error, but if you want to go ahead and try to add one and just see how that ends up looking.
07:24 But it is pretty common to have those types of errors on lots of different forms. We're not going to do that for this exercise, but it is something that I recommend doing on all your forms. In the future, we're actually going to have an abstraction that will handle a lot of this stuff for us. So to review things really quickly,
07:42 by having an action that runs only on the server, we can safely do all the validation that we want and not worry about users being able to circumvent that. Once we've done that validation, if we determine that there are errors, we can return those errors,
07:59 and then those errors can be sent over to our UI via this action data. Then we can grab those errors and render them appropriately in the different areas of our form. Now, there are a couple other small things that I want to do to make some adjustments to this.
08:19 Like one thing we could do is move these types a little bit just to make that nicer. You can feel free to do that yourself, the next step of the exercise that will be done. Then another thing that I like to do is I like to really communicate very clearly what the different return types of my action are,
08:37 and so I almost always will have a status of error. Then I add as const, so that's the status type is literally the string error rather than just being a type of string. This can be really useful, and you'll see this later on as we work through these exercises where we have
08:57 different status types that we return from our action. Then we can key off of that and say, action data.status is error, that now we know for sure that there is action data and it does have an errors value there.
09:14 Then otherwise, we'll say null. To me, this just is a little bit more explicit, and so you'll see in the code that I'm writing in these exercises, that's how I do things is I'll add the status error and as const.
09:30 That is getting some error validation logic that the user cannot circumvent and giving the user a nice error message to boot. It's a good thing.