Next.js Discord

Discord Forum

Next.js 14: Error with useFormState and Server Actions

Answered
maison margiela posted this in #help-forum
Open in Discord
Hi everyone! I'm facing an issue while using useFormState in a client component in Next.js 14.

I have a form where, upon submission, I call a server action that validates the input using Zod. If everything is valid, it sends a magic link. However, I'm encountering the following error:
⨯ Internal error: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Here’s how I’m using useFormState:
const [formState, formAction] = useFormState<FormState, FormData>(
  actions.signInMagic,
  {
    message: "",
    error: null,
  }
);

If i comment out that part of code, the error is gone. Also i include my full component and server action
Answered by !=tgt
you're using a server func with a form so why dont you use <form action>?
View full answer

14 Replies

Black Turnstone
can you share the repo I wasn't able to reproduce the error
Answer
and theres a special useTransition hook that you can use for loading state
check your i18n stuff, you might've missed something, send a ss of the page
@Black Turnstone can you share the repo I wasn't able to reproduce the error
Truly i don't know why i can't reproduce that error again. Is it OK for you if i get in touch with you next time i run into the same problem?
@!=tgt you're using a server func with a form so why dont you use `<form action>`?
thank you for pointing out that i can use straight a form action. I refactored my code to use form actions straight to the point and also using useFormStatus. Code looks much better now. I am still new with next.js, so thank you 😂
There is my approach
function SignInMagic() {
  const t = useTranslations("Login");
  const { pending } = useFormStatus();

  const [formState, formAction] = useFormState<FormState, FormData>(
    actions.signInMagic,
    {
      message: "",
      error: null,
    }
  );

  return (
    <form action={formAction} className="space-y-4">
      <div className="space-y-2">
        <Label htmlFor="email">{t("email")}</Label>
        <Input
          id="email"
          type="email"
          placeholder="your@email.com"
          name="email"
          className="w-full"
        />
        {formState.error && (
          <span className="text-sm text-red-500">{formState.error}</span>
        )}
        {formState.message && (
          <span className="text-sm">{formState.message}</span>
        )}
      </div>
      <Button
        type="submit"
        variant="default"
        className="w-full"
        disabled={pending}
      >
        {pending ? (
          <Loader2 className="mr-2 h-4 w-4 animate-spin" />
        ) : (
          <Mail className="mr-2 h-4 w-4" />
        )}
        {t("magicLink")}
      </Button>
    </form>
  );
}

export default SignInMagic;
lgtm
American
Is that pending really working? I thought useFormStatus must be used in a child to a form
they're using useFormState
which is diff
American
function SignInMagic() {
  const t = useTranslations("Login");
  const { pending } = useFormStatus();
@American function SignInMagic() { const t = useTranslations("Login"); const { pending } = useFormStatus();
yes, you are right, i then changed my code to fix that and now it is working
"use client";
import * as actions from "@/actions";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Mail, Loader2 } from "lucide-react";
import { useFormStatus } from "react-dom";
import { useTranslations } from "next-intl";
import { useFormState } from "react-dom";

type FormState = {
  message: string;
  error: string | null;
};

function SubmitButton({ t }: { t: any }) {
  const { pending } = useFormStatus();

  return (
    <Button
      type="submit"
      variant="default"
      className="w-full"
      disabled={pending}
    >
      {pending ? (
        <Loader2 className="mr-2 h-4 w-4 animate-spin" />
      ) : (
        <Mail className="mr-2 h-4 w-4" />
      )}
      {t("magicLink")}
    </Button>
  );
}

function SignInMagic() {
  const t = useTranslations("Login");

  const [formState, formAction] = useFormState<FormState, FormData>(
    actions.signInMagic,
    {
      message: "",
      error: null,
    }
  );

  return (
    <form action={formAction} className="space-y-4">
      <div className="space-y-2">
        <Label htmlFor="email">{t("email")}</Label>
        <Input
          id="email"
          type="email"
          placeholder="your@email.com"
          name="email"
          className="w-full"
        />
        {formState.error && (
          <span className="text-sm text-red-500">{formState.error}</span>
        )}
        {formState.message && (
          <span className="text-sm">{formState.message}</span>
        )}
      </div>
      <SubmitButton t={t} />
    </form>
  );
}

export default SignInMagic;
if you got a solution make sure to mark it as the answer