safe-action-forms
SKILL.md
next-safe-action Form Integration
Options
| Approach | When to Use |
|---|---|
useAction + native form |
Simple forms, no complex validation UI |
useHookFormAction (RHF adapter) |
Complex forms with field-level errors, validation on change/blur |
useHookFormOptimisticAction |
RHF forms with optimistic UI updates |
Quick Start — Native Form
"use client";
import { useAction } from "next-safe-action/hooks";
import { submitContact } from "@/app/actions";
export function ContactForm() {
const { execute, result, isPending } = useAction(submitContact);
return (
<form
onSubmit={(e) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
execute({
name: fd.get("name") as string,
email: fd.get("email") as string,
message: fd.get("message") as string,
});
}}
>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
{result.validationErrors && (
<p>{result.validationErrors.email?._errors?.[0]}</p>
)}
{result.serverError && <p>{result.serverError}</p>}
{result.data && <p>Message sent!</p>}
<button type="submit" disabled={isPending}>
{isPending ? "Sending..." : "Send"}
</button>
</form>
);
}
Quick Start — React Hook Form Adapter
"use client";
import { useHookFormAction } from "@next-safe-action/adapter-react-hook-form/hooks";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { submitContact } from "@/app/actions";
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
message: z.string().min(10, "Message must be at least 10 characters"),
});
export function ContactForm() {
const { form, handleSubmitWithAction, action } = useHookFormAction(
submitContact,
zodResolver(schema),
{
actionProps: {
onSuccess: () => toast.success("Message sent!"),
},
}
);
return (
<form onSubmit={handleSubmitWithAction}>
<input {...form.register("name")} />
{form.formState.errors.name && <p>{form.formState.errors.name.message}</p>}
<input {...form.register("email")} />
{form.formState.errors.email && <p>{form.formState.errors.email.message}</p>}
<textarea {...form.register("message")} />
{form.formState.errors.message && <p>{form.formState.errors.message.message}</p>}
{action.result.serverError && <p>{action.result.serverError}</p>}
<button type="submit" disabled={action.isPending}>
{action.isPending ? "Sending..." : "Send"}
</button>
</form>
);
}
Supporting Docs
Entry Points
| Package | Entry Point | Exports |
|---|---|---|
@next-safe-action/adapter-react-hook-form |
Default | mapToHookFormErrors, types |
@next-safe-action/adapter-react-hook-form/hooks |
Hooks | useHookFormAction, useHookFormOptimisticAction, useHookFormActionErrorMapper |
Weekly Installs
29
Repository
next-safe-action/skillsFirst Seen
10 days ago
Security Audits
Installed on
opencode29
gemini-cli29
github-copilot29
codex29
amp29
kimi-cli29