gform-react
Back to Integrations

Server Actions (Next.js)

Submit gform-react forms with Next.js Server Actions using the native action prop - no client wiring.

gform-react supports native <form> submission, including Next.js Server Actions. You submit via the action prop instead of onSubmit. gform still runs client validation first and blocks an invalid submit; because each GInput sets the input's name to its formKey, the action reads values from FormData by formKey.

The action

A standard Server Action - signature (prevState, formData) => newState. Read fields by their formKey.

app/actions/sign-in.ts
"use server";
 
export const signIn = async (prevState: unknown, formData: FormData) => {
  const email = formData.get("email")?.toString(); // 'email' = the GInput formKey
  // …authenticate…
  return { success: true };
};

The form

Pass action={formAction} from useActionState. Do not use onSubmit and do not call e.preventDefault() - that would cancel the native submission the action relies on.

EmailForm.tsx
"use client";
 
import { GForm, GInput, GValidator, type GValidators } from "gform-react";
import { useActionState, useEffect } from "react";
import { signIn } from "@/app/actions/sign-in";
 
const validators: GValidators = {
  email: new GValidator()
    .withRequiredMessage("Email is required")
    .withTypeMismatchMessage("Enter a valid email"),
};
 
const initial = {};
 
export function EmailForm({ onSuccess }: { onSuccess: () => void }) {
  const [state, formAction, pending] = useActionState(signIn, initial);
 
  useEffect(() => {
    if (state.success) onSuccess();
  }, [state, onSuccess]);
 
  return (
    <GForm action={formAction} validators={validators}>
      {/* action, NOT onSubmit */}
      <GInput
        formKey="email"
        required
        type="email"
        element={(input, props) => (
          <div>
            <input {...props} />
            {input.error && <small>{input.errorText}</small>}
          </div>
        )}
      />
      <button disabled={pending}>{pending ? "Sending…" : "Send code"}</button>
    </GForm>
  );
}

The contract

  • Use action={formAction} - not onSubmit, and do not call e.preventDefault().
  • gform validates on the client first; an invalid form never reaches the server action.
  • The action signature is (prevState, formData) => newState; read fields by formKey.
  • Each GInput's name equals its formKey, so formData.get("<formKey>") returns the value.
Why no preventDefault?

gform deliberately never calls e.preventDefault() for you. Calling it yourself here would cancel the native form submission that Server Actions depend on. Use action and let the browser submit.

File uploads work too

Because submission is native, file inputs are included in the FormData the action receives - see File Inputs.