Demo of forms with React 19 and Next.js.
If you've wondered...
"Why does the form clear out my input values?"
"How do I pass data from my server action → client?"
"How do I handle client / server validation?"
"How do I display inline errors next to inputs?"
This should help!
So I saw the street from Jamie asking about forms and next JS and React. So I wanted to make a quick demo video where we'll go from this basic form to something a little more complex, talk about client and server validation and some of the other things that Jamie mentioned in their tweet. So let's start with this. On the left, I have a basic HTML form with one input for an address. And on the right I'm going to say, hey, for this address, let me just open up dev tools. We're going to say hello and I'm going to hit enter. Submit this form. It's going to clear out the value, make a GET request back to itself, and update the URL with the value O. We have a query ARAM of address and then we have the value that I entered into the form. This is just a basic default way the browser works with handling forms and we're going to want to extend this a little bit further. As a side note on the form, when we decide what the action is, we could also say something like we want to navigate to a different route. And if I do new here, we're going to navigate to slash search, which I don't have, and still append that query param. But we don't want to do that. So I'm just going to navigate back to where we're at. Instead, we actually want to kind of call some kind of function, some kind of action where we can do some work. So it would be great if I could just use JavaScript for this. So maybe I want to have something like save action that looks good, and then we need to define this function. Save action and inside of here we are going to console log. We'll just do, hey, O, we have this function save action that we want to run on the client. Well, the default, the next shares app router is that this page is going to be a server component. So if I actually went ahead and saved this file, I would expect to see an error that says, hey, by the way, you probably wanted to have use server. You wanted to mark this entry point to the server to use this function where you're going to do something like talk to a database. Well, to demonstrate something here. You can actually mark the entry point into your page as a client component. So if I go to the top, I mark this as use client. What we're telling next, yes, is actually we're going to mark the entry to our entire page as a client component. That makes not only the form and the input, but also this action turned into a client action. So if I open up the console here and I say just anything and submit the form, I see, hey, so I see our console log in the browser in the client's environment. But we probably want to talk to a database or something like that, so we don't want to do this. Instead we want to mark this as use server. And that's going to say, hey, we have this function that our form can basically spin up like an API endpoint without you having to do the wiring yourself. So when the form submits, it can go call this API or this server action. And this is going to need to be an async function. So if I save here, I reload the page again and I just submit this. Now we see the console log on the server. So it's kind of cool how you can move that same programming model between the client and the server. But let's actually make. Just a little bit more feature rich here and move over to a more advanced example, which is the address form. OK, so I just imported this address form component that I made in V0, just using shad CNI components for the inputs and the labels and the cards. And we're going to talk about how this works. It has some client and server validation and kind of step through the code. The first thing is when you're doing a lot of form logic with React 19, you're going to be using things like use action state. These hooks that are designed for client components, it's not a bad thing. It's not a wrong thing. If you have a server component entry into your application like this page where maybe you even marked this as an async component and you're fetching some data in here and forwarding it to your client components that are below, there's totally fine. So address form is a client component. And inside of here we have just some normal HTML elements and some shade CN UI components for the. Form and I want to talk through kind of each individual bit on here the first question that this tweet had was how do I get the form to reset when I submit it So let's actually just give this a shot here. I'm going to try to save the address and it's going to say hey this field is required which is client or browser validation of the form so I'll enter in something here it actually had. Let's do some city. Please fill out this field will do a state zip code. Ohh, it looks like it also is expecting multiple digits. We'll do five hit save. Oh no, we need a country 2. Now we can save and it saves this value to the database, so you'll notice all of the values in the form reset. This is the default behavior of using forms in React. In Next JS, it's mimicking the same behavior that you get out of the browser. Most of the time you want this, but sometimes you don't. For example, if you have errors. Ideally, I don't want to have to fill out all of those things again. For example, let me go to our input element where we used some browser validation. Highly recommended to have this. By the way, you want to have both client and server validation. But let's say I didn't. Let's say I took off the required field the min length. Max length. Let's just get rid of those. I'll reload this page. I'll try to submit. So now it's not going to give me the validation in the browser. So I'll do this one. I'll do this one. I hit save. So now we're actually going to go to the server. We have our server validation, it comes back with errors and the errors are saying, hey, St. addresses required. There were some errors in the form, but we lost all of the values that we had currently. So I'm going to show you what the fix is for this and then we're going to talk more about how it works. So use action state is the hook inside of my component that allows me to connect some state between my server action and the actual. Component that's rendering it. This is how you return those values and pass them back and forth. So I'm passing in some action which is submit address and I get back this action that I forward to my form action. So there's this state that we derive back that we use inside of our component. You notice we're using it here for the different errors, but we can also use that state to hold the temporary values that we've submitted. So if I go to our server actions file, what we want to do is reuse the values that we submitted to our server action. We want to send those back to the client. So for example, in this block where we're saying there were form errors, so the validation failed, Really what we want to do is send. Something like inputs, which are going to be the raw data. So the data that we passed to the server action, we're going to say, you know what, send that back to the client. Let me save this. And then inside of our address form, what we want to do is read the value from the state that we've derived from use action state. So we could do something like on the city, we could do default value is going to be state dot inputs and this is going to be city. So let me save and as a reminder, the street address. Is still does not have client validation. It does have server validation. So we're going to intentionally leave it blank and we're going to say the city is going to be this this 12345. Now I'm gonna hit save. And what you're going to notice is that the ZIP code, the state, the country, they all were reset. There was a server error for street address, but the city value, the city value persisted. It was able to take the value from the server action, put it into use action state, and then I can read the value. So what that means, and I'll just Fast forward here, is that we want to put this default value on all of the fields. So now that I've added them all, let's do a quick demo. Again, I'll leave street address blank. We'll say this is 123-1234. Five and then something else here. Hit save. OK, well, I guess we need more and I guess we need more here. OK so the street address we didn't provide anything. We had the server side validation but notice all of the other inputs retain their value from before. So now I can go in and actually give some address here. And everything works now. You'll also notice there was a couple of other goodies I didn't get a chance to talk to in here, but we've added the right autocomplete values if we want to fill this with one password or LastPass or the Passwords app. And we also have these nice errors coming from our state as well that show underneath the input. So just a quick little example of how you can do both server side and client side validation with forms in React. And next, yes, I hope that answers the question here. Peace.
Lee Robinson, the thing that bothers me here is that useActionState does not provide a reset functionality.
I've been using the same setup but when the form has been submitted successfully I remove it from the DOM and show a success message instead. That message has a `reset` button that should show the form again.
My first instinct was to put a `key` on the component in the parent and pass in a function that generates a new key. But I ultimately solved this by adding a second form that calls the same action with a hidden input of value/name reset that returns the default object from the server.
This response also returns `reset: true` and triggers a useEffect to reset my client side validation. It would be nice if we didn't have to call the server for something like that.
Lee Robinson for persisting values across form submissions, what about storing the values in useState or some other state container? Do you think it’s better to pass the values back from the action as opposed to using controlled inputs?
I think both are viable options. It seems to me like if you don’t really need to read the values elsewhere in the component (while the user is typing) then the approach you cover is preferable.
Thanks for your and the team’s hard work on NextJS and Vercel :)
This helps a lot! Thanks for this. A quick question: Usually I tend to use React Hook Form for this kind of forms. I assume, for this kind of form, there is no need for an extra library. What do you think?
I have a good question. My client wants to use the form and play with the data without saving it to the backend but wants to get the live validation. and have a disable button Submit if the fields contain an error.
how do you solve this problem with the new trend?
Seasoned software engineer with expertise in full-stack development, cloud infrastructure, and frontend technologies. Proven ability to drive project success through leadership, coding excellence, and automation.
Front-end web developer
1wLee Robinson, the thing that bothers me here is that useActionState does not provide a reset functionality. I've been using the same setup but when the form has been submitted successfully I remove it from the DOM and show a success message instead. That message has a `reset` button that should show the form again. My first instinct was to put a `key` on the component in the parent and pass in a function that generates a new key. But I ultimately solved this by adding a second form that calls the same action with a hidden input of value/name reset that returns the default object from the server. This response also returns `reset: true` and triggers a useEffect to reset my client side validation. It would be nice if we didn't have to call the server for something like that.