Creating a Server Action for Form Data
So far, we've explored two out of four different types of interaction with the server: an API endpoint that takes JSON and another that takes form data.
Now we'll dive into creating a server action and using it to submit data.
Creating a Server Action
Server actions can be placed within a React server component, like a page, or within a separate file. For now, we'll create the server action inside src/app/page.tsx.
The server action will be an asynchronous function named onDataAction that we'll pass into the RegistrationForm client component:
However, there's an error underneath the onDataAction prop because it isn't defined as a property on RegistrationForm.
On the page in the browser, we are shown an error:
Next.js thinks that onDataAction is an event handler because it doesn't know that it's a server action.
In order to resolve this, we need to include "use server"; inside of the onDataAction function to notify Next.js that the function is server-only:
With this change, the error goes away in the browser but TypeScript isn't happy that onDataAction not being defined as a property on RegistrationForm.
Let's fix it.
Adding onDataAction to RegistrationForm
Over in RegistrationForm.tsx, we'll define onDataAction as a function that takes data and returns a promise. For now we'll broadly define the data type as any, and the return type will be Promise<void> since nothing is returned:
With the onDataAction function defined and wired up, we can test it out.
Testing the Server Action
On the web page, fill out the form and hit submit.
Nothing seems to happen in the browser console where we previously saw our console.log() output.
Instead, we'll check the terminal and see that our data has successfully been sent to the server:
Now that we know our submit is working properly, let's add the proper types to our server action.
Adding Typing and Validating Data
Back in page.tsx we import z from Zod and our schema:
Inside the onDataAction function, we'll type data using the z.infer trick we've used before. We also will use the schema's safeParse to parse the data and use the conditional logic to check if the parsing was successful:
Now that we've made these changes, TypeScript gives us another error on the onDataAction prop in page.tsx because the output of the function isn't void anymore. It's now an object that contains either a success message and user data, or an error message and the issues.
In RegistrationForm.tsx, we need to modify the promise to match the new schema.
The Promise will now return an object that will contain a message, and might contain a user or issues. The user will be inferred from the schema, and issues will be a string array:
We'll also update the onSubmit function to log out the output. Remember to use await since we're dealing with a promise:
Testing our work in the browser, we can submit the form and see the output in the console as expected.
Wrapping Up & Next Steps
We've now completed the third technique for form data handling, this time using a Server Action.
For our fourth method, we will reuse form data again by rewriting the existing code to use it.
Follow the included resources below to try it yourself, then check back in the next video to see how I did it.
Resources for Posting Form Data to a Server Action
In order to post form data to the server action, you'll be able to reuse much of the code from the onDataAction to create a new onFormAction function.
The new onFormAction function will accept FormData which will be parsed after being turned into a simple object with Object.fromEntries.
Then over on the client side, you'll need to update onSubmit function to create a formData object based on the submitted data, then call formAction with the formData as an argument.
As an alternative, you could use the useFormState hook and React refs to manage the form state and submit the form data.
We'll look at both solutions in the next video.