How to Pass Additional Arguments to Next.js Server Actions
How to Pass Additional Arguments to Next.js Server Actions êŽë š
Asynchronous data mutation and handling is a necessary task in modern web applications. You may want to execute a standalone asynchronous function on the server to carryout tasks like saving data to the data store, sending emails, downloading PDFs, processing images, and so on.
Next.js provides us with Server Actions
which are asynchronous functions that execute on the server. We can use server actions for data mutations on the server, but server actions can be invoked from both server and client components.
Server actions are a great way to handle form submissions by executing the action when the form data gets submitted. In this article, we will look into a practical use case of handling additional arguments in Next.js server actions.
If you are interested in learning Next.js Server Actions with design patterns and project building, I have created a crash course for you that you can find here.
Also, this article is also available as a video tutorial here:
Why Would You Need to Pass Additional Arguments?
When we execute a server action on a form submission, the server action gets the form data automatically. For example, take a look at the form below:
<form className="p-4 flex" action={updateUser}>
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
Here, we are executing a server action called updateUser
when the form gets submitted. The updateUser
function will receive the submitted form data as an argument which can be used to extract the form field values.
As you see in the code snippet below, the updateUser
function gets a formData
as an argument, and we can extract the value of the name
field from it.
"use server"
export async function updateUser(formData) {
const name = formData.get('name');
console.log(name);
}
While this pattern covers most of the basic use cases, you may need to pass additional arguments programmatically to the server actions. These arguments are not part of the form or form data or user input data. They may be programmatically passed values to your server action.
To understand this, check the server action code snippet below. Itâs the same server action we have seen before, but we have passed an additional userId
argument along with the regular formData
argument.
"use server"
export async function updateUser(userId, formData) {
const name = formData.get('name');
console.log(userId);
console.log(name);
}
The userId
value is something internal to the application â and you wouldnât ask a user to submit the value as part of the form submission. Rather, you may need to pass it programmatically to your server action to perform further computations.
Right, thatâs the use case weâre talking about. As we understand why we need it, letâs understand how to achieve it. But first, letâs create a form and a working server action for it.
A Form With a Server Action
Create a directory called actions
under the app
directory of your Next.js application. Now create a user.js
file under the actions
folder with the following code:
"use server"
export async function updateUser(formData) {
const name = formData.get('name');
console.log(name);
// Do anything with the name, save in DB, create invoice, whatever!
}
This is how you create a server function in Next.js. It must have a âuse serverâ
directive at the top of the file to tell Next.js that this is a special file with one or more asynchronous functions to execute on the server.
Then we have the server action (the async function) updateUser
with formData
as the argument. Inside the function definition, we extract out the name
value and print it on the console.
Letâs now attach this server action to a form. To do that, create a folder called components
under the project root folder. Create a file called user-form.jsx
with the following code:
import { Input } from "./ui/input"
import { Button } from "./ui/button"
import { updateUser } from "@/app/actions/user"
const UserForm = () => {
return(
<form className="p-4 flex" action={updateUser}>
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
)
}
export default UserForm;
This is a simple React component with a form. The form has one input text field called name
and a submit button to submit the form. The form has an action
attribute with the server action updateUser
as the value. Now, when the form gets submitted with a name
value, the server action will get it as part of the form data as we discussed above.
Letâs test it out. To do that, weâll create a Next.js route and page where we can use the UserForm
component. Create a folder called extra-args
under the app
directory. Now, create a file called page.js
under the app/extra-args
directory with the following code:
import UserForm from "@/components/user-form";
const ExtraArgsDemo = () => {
return (
<UserForm />
)
}
export default ExtraArgsDemo;
This is a simple React component where we have imported the UserForm
component and used it in the JSX. Now run the local server and access this route localhost:3000/extra-args
. You should see the form with a text field and a button.
Type some text inside the text field and click on the button.
Now, you will be able to see that the typed text has been printed on the server console. Why on the server console? Why not on the browser console? This is because server actions execute on the server, not on the client side browser.
So, with this we have now established a data flow like this:
Page => Form => Server Action
The page has a form. The form execute a server action on submission. The server action prints a form data on the server console.
Letâs now enhance these pieces to pass additional arguments to the server action.
How to Pass Additional Arguments
Letâs pass a prop to the UserForm
component from the page. Weâll pass a userId
with a value to pretend that we are passing this userId programmatically to our form and to the server action from there.
import UserForm from "@/components/user-form";
const ExtraArgsDemo = () => {
return (
<UserForm userId={"1234"} />
)
}
export default ExtraArgsDemo;
In the UserForm
component, we accept the userId
prop. Now, we have to do something special to pass this userId to the updateUser
server action.
JavaScript has a magical method called bind()
that helps us create a Partially Applied Function
. With this partially applied function, you can create a function from another functionâs preset arguments.
In our case, the updateUser
function already has an argument called formData
. Now we can pass userId
as the additional argument using the bind()
method to create a new function.
const updatedUserWithId = updateUser.bind(null, userId);
The first argument of the bind()
method is the context you are binding the function to. The context handles the association of the function with the this
keyword value. In our case, we can keep it null
as we are not changing it. After that, we passed the new argument userId
. Itâs good to know that the bind()
method works on both server and client components.
Here is the modified UserForm
component (user-form.jsx
file). Note that the form action value is now modified to the new function updatedUserWithId
.
import { Input } from "./ui/input"
import { Button } from "./ui/button"
import { updateUser } from "@/app/actions/user"
const UserForm = ({userId}) => {
const updatedUserWithId = updateUser.bind(null, userId);
return(
<form className="p-4 flex" action={updatedUserWithId}>
<Input className="w-1/2 mx-2" type="text" name="name" />
<Button type="submit">Update User Name</Button>
</form>
)
}
export default UserForm;
Now, the server action will receive the userId
value as an argument. Letâs print that to the console as well.
"use server"
export async function updateUser(userId, formData) {
const name = formData.get('name');
console.log(userId);
console.log(name);
// Do anything with the user id and name, save in DB,
// create invoice, whatever!
}
Now if you submit the form with a name value:
Youâll see that both userId and the name values are logged into the server console. Great! We have logged one value from the form data, and the other one was passed internally to the server action.
So, we learned how to pass the extra arguments to the server action along with the form data.
What About the Hidden Fields?
HTML supports a hidden type form field to pass client data to the server without accepting the input from the users. So this means that we could have used the hidden field to pass the userId
value like this:
So why did we do all that with the bind()
method? Well, because of security concerns. When you pass data using hidden fields, the value will be part of the rendered HTML, and it will not be encoded as well. So itâs better to handle it programmatically.
Resources
That's all for now. Did you enjoy reading this article and have you learned something new? If so, I would love to know if the content was helpful. Let me share a few additional resources you may need:
- All the Source Code used in this article is on my GitHub.
- Hereâs the Server Action Crash Course with Patterns and Project.
- Hereâs the Server Action Official Documentation if you want to read more.
- And you can read more about the bind() method here.
Additionally, you can connect with me by:
- Subscribing to my YouTube Channel (
tapasadhikary
). If you are willing to learn React and its ecosystem, like Next.js, with both fundamental concepts and projects, I have great news for you: you can check out this playlist on my YouTube channel with 25+ video tutorials and 15+ hours of engaging content so far, for free. I hope you like them as well. - Following me on X (
tapasadhikary
) or LinkedIn (tapasadhikary
) if you don't want to miss the daily dose of up-skilling tips. - Checking out and follow my Open Source work on GitHub (
atapas
). - I regularly publish meaningful posts on my GreenRoots Blog, you may find them helpful, too.
See you soon with my next article. Until then, please take care of yourself, and keep learning.