State Management at Scale in React & Next.js

Converting useState to FormData

State Management at Scale in React & Next.js

Lesson Description

The "Converting useState to FormData" Lesson is part of the full, State Management at Scale in React & Next.js course featured in this preview video. Here's what you'd learn in this lesson:

David discusses how to simplify form handling using built-in browser and Next.js form tools, emphasizing the use of form data and server-side validation. He demonstrates replacing useState with a status string enum and using the useActionState hook for managing form states efficiently.

Preview
Close

Transcript from the "Converting useState to FormData" Lesson

[00:00:00]
>> David Khourshid: For the next exercise, I'm not going to actually have you do this, but we're going to be doing it together. I just want to show you how we could use these native form tools that we have built into the browser and built into Next JS instead of having to manage a lot of use dates with those forms.

[00:00:20]
So gonna get rid of that and then we are going to go to see exercise form page. And over here we, we definitely have a bigger form. So we are, we're going to definitely simplify this a lot. At the very least, we do have our combined data over here.

[00:00:46]
So we have our travel form data. And just to show you what this looks like in the ui, if we go to form, this is a form for entering travel information and there's a lot of fields in here, which is why I'm showing you this pattern. Because doing this with just use dates, it can be tedious.

[00:01:05]
You have to have a use date for first name, last name, birthday, Passport, Origin City, etc. I feel like 2000. Yeah, I feel old because 2000 I think, okay, there's kids that are born in 2000, but it's all grown adults. It's very weird. But anyway, you know, you have your passport number there and the idea behind this is we want to have server side validation and that's what you see coming back over here.

[00:01:36]
But in this, in this form you can also submit your travel data and then you could submit another form and then, you know, keep going like that. So our initial implementation, we're using useState a lot over here we have these Boolean flags, issubmitting issuccess. Hopefully you immediately recognize that you can change that for a simple status string enum and same thing over here you could use type states and discriminated unions to combine the status with the data.

[00:02:14]
But we're going to be simplifying this even further than that. So again, using the strangler fig pattern that I talked about, we're going to start by saying const state submit action which already exists and I'll show you in a minute and is pending and we're going to be grabbing that from use action state, which is a react hook.

[00:02:36]
Again, you have your submit travel data and also you're going to be passing in an initial state. And just to show you what this initial state looks like, we have our, you know, we have all of our information here. Wait a minute. Okay, let me just make sure that we have the right initial state over here.

[00:03:01]
So this is Actually going to be coming from somewhere else. Okay, so initial state formstate equals status idle, no errors and data null, and so you'll see how this is used in a minute. But the idea is that all of our inputs should already. I'm going to go ahead and hide this.

[00:03:31]
But all of our inputs should already have names. And so that's going to be the thing that really powers using form data is the fact that our inputs and all of our fields have names that are going to be fed into the form data that we derive from it.

[00:03:46]
So instead of onsubmit, instead we're going to put action and this action is going to be our form action that we have over here. So just going to the top submit action. And then what I'm going to do here instead of deleting everything first is I'm going to console log the states.

[00:04:11]
Just so you see what we're dealing with. So we have the state over here and we are also going to console log is pending over here. All right, so now we're going to, first of all, let's make sure we're running this. All right, so we're in our exercise form.

[00:04:38]
I'm going to open up the console over here. We have is pending as false, we have our initial state object. So let's go ahead and enter some details. Again, the state is changing and if we submit this, it is going to hit the even though it's invalid, it's going to hit that server action and we're going to get some data back from the server.

[00:05:06]
So we're gonnasee a bunch of errors. We're going to see that the birth date's invalid, the origin city is invalid, et cetera, but it is hitting that server and we also get a status back. So where does this happen? Let's take a look at form and then actions.

[00:05:27]
So when we submit travel data, first of all, we're getting this raw data and we're getting it from the form data that it receives. So we could validate the data using Zod, like we talked about, and then we could use that to generate errors. And so if we have errors on specific pieces of data, then we could pass that back.

[00:05:51]
However, if everything goes well, we sort of have a mock API over here. But if all goes well, we have the success and we're also returning the data from there. And if we have an error, then we're actually giving back the raw data because we don't want to completely blow away the form states.

[00:06:13]
We want them to be able to correct their errors. So in this case we're not managing the status inside of the actual ui, but instead we're managing it on the server side. So we're saying the status is success. And in this case we also have results data. So if we go back here and actually fill everything out, so 2000, not my actual birthday or passport number and I don't know who would ever pick middle, but it's an option in some airlines.

[00:06:47]
But once you submit this, it's not happening because we didn't update it in the ui. But you could see over here that we have state of success. We have our data coming back, we have no errors and we can use that in our, in our application. So just like what I, what we did previously, instead of using this issuccess use date, we could derive that.

[00:07:14]
So const issuccess equals state status equals success. Same thing here, instead of errors we could say const errors equals state errors, and same thing over here, instead of is submitting, we could say const issubmitting equals if the state dot status is pending. And now we have a bunch of errors, but we could just comment those out because we're actually no longer using this function at all.

[00:07:55]
So we're just going to comment that out. So now when we fill out our form again, set the birth date, passport number, origin, city, seat preference. Okay, let's put 2000 now let's debug this together. We could see that we have a status of success, but it is not showing us success.

[00:08:26]
It's if we have success data as well. So that is still in the use dates, so we need to derive that too. So const success data equals dates data, so now one more try. David K,
>> David Khourshid: It's my actual birthday [LAUGH].
>> David Khourshid: I love sitting in the aisle.

[00:08:54]
Okay, now we have our successful travel data and now we could actually get rid of basically a lot of things. We could get rid of all of these use dates and all this unused code and even this like the whole setformdata thing where we basically don't need that anymore, so we could get rid of that.

[00:09:21]
And so now all of these can just be uncontrolled components, and so you could see that this really simplifies how we work with simple forms. And then, yeah, we no longer need this handle submit because everything happens inside of that server action. We no longer need this update form field helper.

[00:09:49]
So update form field again, where this is going to be just uncontrolled data. So we don't need that either we're actually going to turn on Claude and have it help us out here. Don't need this or this or any of these. So we could get rid of that and get rid of that as well.

[00:10:12]
So now you see that we really, really simplified our form. This might not even be necessary if you use the state status directly. But everything is just a lot, you know, it's a lot simpler. And in fact this is pending, this is redundant because we don't need issubmitting.

[00:10:35]
We could just, you know, basically have that as is pending. So we have all this issubmitting which we could change to is pending and then be all good. And so now there's a lot of unused things that we could remove and our code just becomes a lot simpler.

[00:10:58]
So that is how we could use the use action state hook and use built in server actions to handle forms. And also at a basic level, using form data for basic forms where you don't need anything special with a form, you just need to capture data.
>> Male Speaker 2: Is it possible to have immediate blur focus of validity checking for those inputs when they are not controlled?

[00:11:25]
>> David Khourshid: That's a good question. So two things. First of all with server actions it's sort of, or you know, form actions as it's called in React it assumes that you're going to basically submit your form all at once, so it's not going to be doing any validation on blur.

[00:11:45]
The second thing too is that when you have uncontrolled components and you want to basically keep track of validation errors on Blur, I recommend you use some of the patterns that we learned about earlier. So for example, we could do things like const, let's call these validation errors set validation errors and then we will have a use date for that.

[00:12:12]
And this is somewhere where use data is appropriate because this happens not during the client server interaction, but it instead happens only on the client. So what we could do is let's say that, you know, we're, let's just pick one of these fields. So for example, first name.

[00:12:34]
First of all, if you can use HTML5 validation then this is something that I strongly recommend you do. So this is for example, if you add required or if you add a pattern over here just to make sure that the data is valid, like this just says it should be more than one character.

[00:12:53]
So basically it's like it has to be there and it has to be a valid name, then I would recommend you do that. Otherwise you could just. On Blur you have the event target dot value and so you could say const value equals event target value. And then you could do validation directly in there.

[00:13:18]
So you could set the validation errors. For example, if it's less than 3, you have that over there. Just be sure to handle what happens in terms of showing that. So for example, if this is focused, you don't want to necessarily show that error. Or maybe you do, and maybe it goes away once you blur from that again.

[00:13:37]
But this is just an example of how you can do validation on blur in an uncontrolled component, because you have that value right there. All right, so we were just talking about form handling with form data and server actions and how we could basically use this for simple forms, forms that don't have that many requirements.

[00:14:00]
But there was an excellent question in the chat where it was about managing large forms or form states with a lot of dynamic validity or maybe async validation. Or there's a long time ago I did write a form library called Redux Simple Form. So I do know about like just all of the different potential use cases and edge cases of complex forms.

[00:14:23]
You have nested forms, you have dependent fields, async validation, masking, things like that. So forms get very, very complicated, the question was about how would you handle that, or are there resources which talk about complex forms, date management solutions and best practices around that? Yeah, it's true that when you're dealing with complex state management, the usual culprit is actually forms, either complex forms or multi step forms in your application.

[00:14:59]
And so the way that we could approach this is first of all, if you have a simple form, try to keep things simple, again, the default is you would probably use date for each of those individual fields. You can apply the previous lessons we talked about and first of all combine those use dates into one use date, just to make things a lot simpler.

[00:15:22]
And also when you're sending that data over, you know, for form submission, it's all in one object, so it becomes really easy. Or you can take it to the next level and use form data with Zod to validate that form input. So then you get that, you get all of these form inputs and you get that validation for free just by using form data.

[00:15:43]
And it works naturally with server actions for frameworks such as Next js which allow you to natively handle those forms. But of course there's use cases like I talked about with async validation, complex forms, et cetera. We're going to be talking about data normalization later, which simplifies the nested form aspect a lot.

[00:16:06]
But when you're dealing with more complex form requirements, I recommend you use a library that already handles this for you. Such as, there's Tanstack form, there's React Hook form and there are similar libraries. What I would not recommend is trying to roll this yourself because again, there's a lot of edge cases and when you roll your own form based abstractions, first of all, you're probably reinventing the wheel.

[00:16:32]
Forms are one of the most common things in web applications, so try not to do that. And secondly, when you're working with teammates, it's better to point them at documentation, e.g., TanStack Form or React Hook Form, rather than trying to maintain your own documentation for your own abstraction that you built.

[00:16:53]
The second thing too is things become a lot less complicated when you go based on the first principle that we talked about, which is events are the actual source of truth. So thinking about, for example, async validation, it sounds really complex and really scary. But when you think of it as I put an input in there and then something goes off to a server and comes back with data saying whether it's valid or not.

[00:17:19]
Now we have, going back to our diagrams, you could think of it as a sequence diagram. We have our form and we have some sort of validation API that we're hitting. And we're also thinking about events too. So we have an event where we're updating the state and then we have an events coming back, so then you could use patterns like useReducer to actually handle that async validation if it's not handled already in those form libraries that I talked about.

[00:17:49]
So again, data normalization and also treating everything as an event and handling the events rather than treating async validation as something special, that's gonna really help you a lot with complex forms.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now