safe-action-forms

Use when integrating next-safe-action with forms -- react-hook-form adapter (useHookFormAction, useHookFormOptimisticAction, mapToHookFormErrors), native HTML…

INSTALLATION
npx skills add https://github.com/next-safe-action/skills --skill safe-action-forms
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

export function ContactForm() {

const { formAction, result, isPending, hasSucceeded } = useStateAction(submitContact, {

onSuccess: () => toast.success("Message sent!"),

});

return (

{result.validationErrors?.email && (

    <p>{result.validationErrors.email._errors?.[0]}</p>

  )}

  {result.serverError &#x26;&#x26; <p>{result.serverError}</p>}

  {hasSucceeded &#x26;&#x26; <p>Message sent!</p>}

  <button type="submit" disabled={isPending}>

    {isPending ? "Sending..." : "Send"}

  </button>

</form>

);

}

Note: `useStateAction` requires the server action to be defined with `.stateAction()` instead of `.action()`. See the [hooks skill](../safe-action-hooks/use-state-action.md) for the full decision table on when to use `useAction` vs `useStateAction`.

## 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 &#x26;&#x26; (

<p>{result.validationErrors.email?._errors?.[0]}</p>

)}

{result.serverError &#x26;&#x26; <p>{result.serverError}</p>}

{result.data &#x26;&#x26; <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 &#x26;&#x26; <p>{form.formState.errors.name.message}</p>}

<input {...form.register("email")} />

{form.formState.errors.email &#x26;&#x26; <p>{form.formState.errors.email.message}</p>}

<textarea {...form.register("message")} />

{form.formState.errors.message &#x26;&#x26; <p>{form.formState.errors.message.message}</p>}

{action.result.serverError &#x26;&#x26; <p>{action.result.serverError}</p>}

<button type="submit" disabled={action.isPending}>

{action.isPending ? "Sending..." : "Send"}

</button>

</form>

);

}

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card