Form Actions
You can utilize Server Actions as Form Actions too. next-safe-action allows you to define stateful or stateless Form Actions.
FormData
input
For defining actions with FormData
input, the recommended approach is to use the zod-form-data
library, which allows you to do that. In these two examples below we'll be using it.
Stateless Form Actions
You can define a stateless safe action using the action
instance method, and then pass it to the action
prop of a form using direct execution
, useAction
hook or useOptimisticAction
hook.
With this method, you can access previous result from the client component, both by awaiting the safe action or by using the result
prop returned by the hooks. You can't access previous result on the server, though, and this is why this approach is called stateless: the server doesn't know the previous result of the action execution.
Here's an example using the useAction
hook:
"use server";
import { actionClient } from "@/lib/safe-action";
import { z } from "zod";
import { zfd } from "zod-form-data";
const schema = zfd.formData({
name: zfd.text(z.string().min(1).max(20)),
});
export const statelessFormAction = actionClient
.schema(schema)
.action(async ({ parsedInput }) => {
await new Promise((res) => setTimeout(res, 1000));
return {
newName: parsedInput.name,
};
});
"use client";
import { useAction } from "next-safe-action/hooks";
import { statelessFormAction } from "./stateless-form-action";
export default function StatelessForm() {
const { execute } = useAction(statelessFormAction);
return (
<form action={execute}>
<input type="text" name="name" placeholder="Name" />
<button type="submit">Submit</button>
</form>
);
}
You can also find this example in the playground app: stateless form action example.
Stateful Form Actions
You can define a stateful safe action using the stateAction
instance method, and then pass it to the action
prop of a form using the useStateAction
hook.
With this method, you can access previous result both from the client component, by using the result
prop returned by the hook, and on the server, where you define the action. More information about that in the stateAction
and useStateAction
sections.
Note that if you want or need to use stateful actions:
- You must define them with
stateAction
instance method. This changes the signature of the Server Action function, placing theprevResult
as the first argument. - If you're on Next.js < 15, you can manually pass them to
useFormState
hook, which will be deprecated. - Starting from Next.js 15, you should use the built-in
useStateAction
hook (which uses React'suseActionState
hook under the hood) exported fromnext-safe-action/stateful-hooks
path.
Here's an example of a stateful action, using the useStateAction
hook:
"use server";
import { action } from "@/lib/safe-action";
import { z } from "zod";
import { zfd } from "zod-form-data";
const schema = zfd.formData({
name: zfd.text(z.string().min(1).max(20)),
});
// Note that we need to explicitly give a type to `stateAction` here, for its return object.
// This is because TypeScript can't infer the return type of the function and then "pass it" to
// the second argument of the server code function (`prevResult`). If you don't need to access `prevResult`,
// though, you can omit the type here, since it will be inferred just like with `action` method.
export const statefulFormAction = action
.schema(schema)
.stateAction<{
prevName?: string;
newName: string;
}>(async ({ parsedInput }, { prevResult }) => {
return {
prevName: prevResult.data?.newName,
newName: parsedInput.name,
};
});
"use client";
import { useStateAction } from "next-safe-action/stateful-hooks";
import { statefulFormAction } from "./stateful-form-action";
export default function StatefulForm() {
const { execute } = useStateAction(
statefulFormAction,
{
initResult: { data: { newName: "jane" } }, // optionally pass initial state
}
);
return (
<form action={execute}>
<input type="text" name="name" placeholder="Name" />
<button type="submit">Submit</button>
</form>
);
}
You can also find this example in the playground app: stateful form action example.