Registration form with useActionState

This form demonstrates the use of the useActionState hook in React for managing form state and submission. The hook can be used both with server- and client components. It reduces a lot of complexity in handling all the form states, where you can use uncontrolled components more easily.

The components in the example below don‘t have to manage their own state, as the hook takes care of that.

Gender
Code example
import { useActionState } from "react";

type FormValues = {
  name?: string;
  email?: string;
  gender?: string;
  country?: string;
  termsAccepted?: string[];
  formSubmitted?: boolean;
};

async function handleSubmit(_: unknown, data: FormData) {
  const formValues = {
    name: (data.get("name") ?? "").toString().trim(),
    email: (data.get("email") ?? "").toString().trim(),
    gender: (data.get("gender") ?? "").toString(),
    country: (data.get("country") ?? "").toString(),
    termsAccepted: data.getAll("termsCheckboxes"),
  } as FormValues;

  const isIncomplete =
    ["name", "email", "gender", "country"].some(
      (key) => !formValues[key as keyof typeof formValues]
    ) ||
    !formValues.termsAccepted?.includes("termsAccepted1") ||
    !formValues.termsAccepted?.includes("termsAccepted2");

  if (isIncomplete) {
    return formValues;
  }

  // Simulate form submission
  await new Promise<void>((resolve) => {
    setTimeout(() => resolve(), 1000);
  });
  console.log("Form submitted successfully:", formValues);
  return {
    formSubmitted: true,
  };
}

const RegistrationForm = () => {
  const [formData, setFormData, isPending] = useActionState(handleSubmit, null);

  return (
    <form action={setFormData} className="mb-form flex flex-dir-col gam">
      <NameComponent formData={formData} />
      <EmailComponent formData={formData} />
      <GenderComponent formData={formData} />
      <CountryComponent formData={formData} />
      <TermsComponent formData={formData} />
      <button disabled={isPending} type="submit">
        {isPending ? "Submitting..." : "Register"}
      </button>
      {formData?.formSubmitted && (
        <p className="success">Registration successful!</p>
      )}
    </form>
  )
}

export default RegistrationForm;