A simple React server action builder that provides input validation with zod.
# npm
npm install server-act zod
# yarn
yarn add server-act zod
# pnpm
pnpm add server-act zod
// action.ts
"use server";
import { serverAct } from "server-act";
import { z } from "zod";
export const sayHelloAction = serverAct
.input(
z.object({
name: z.string(),
}),
)
.action(async ({ input }) => {
return `Hello, ${input.name}`;
});
// client-component.tsx
"use client";
import { sayHelloAction } from "./action";
export const ClientComponent = () => {
const onClick = () => {
const message = await sayHelloAction({ name: "John" });
console.log(message); // Hello, John
};
return (
<div>
<button onClick={onClick}>Trigger action</button>
</div>
);
};
// action.ts
"use server";
import { serverAct } from "server-act";
import { z } from "zod";
export const sayHelloAction = serverAct
.middleware(() => {
const userId = "...";
return { userId };
})
.input(
z.object({
name: z.string(),
}),
)
.action(async ({ ctx, input }) => {
console.log("User ID", ctx.userId);
return `Hello, ${input.name}`;
});
useActionState
Documentation:
We recommend using zod-form-data for input validation.
// action.ts;
"use server";
import { serverAct } from "server-act";
import { z } from "zod";
import { zfd } from "zod-form-data";
export const sayHelloAction = serverAct
.input(
zfd.formData({
name: zfd.text(
z
.string({ required_error: `You haven't told me your name` })
.max(20, { message: "Any shorter name? You name is too long 😬" }),
),
}),
)
.formAction(async ({ formData, input, formErrors, ctx }) => {
if (formErrors) {
return { formData, formErrors: formErrors.formErrors.fieldErrors };
}
return { message: `Hello, ${input.name}!` };
});
// client-component.tsx
"use client";
import { useActionState } from "react";
import { sayHelloAction } from "./action";
export const ClientComponent = () => {
const [state, dispatch] = useFormState(sayHelloAction, undefined);
return (
<form action={dispatch}>
<input
name="name"
required
defaultValue={state?.formData?.get("name")?.toString()}
/>
{state?.formErrors?.name?.map((error) => <p key={error}>{error}</p>)}
<button type="submit">Submit</button>
{!!state?.message && <p>{state.message}</p>}
</form>
);
};