Next.js Discord

Discord Forum

Server Actions -- Form

Unanswered
Singapura posted this in #help-forum
Open in Discord
SingapuraOP
Hello,

I am trying to use the example -- https://github.com/vercel/next.js/tree/canary/examples/next-forms to build forms in Nextjs App Router with Server Actions & Mutation with Forms, but whenever I am hitting submit button, the page is getting refreshed and going to home page, any help on this?

16 Replies

SingapuraOP
"use server";

import { revalidatePath } from "next/cache";

export const sendContactEmail = async (_state: { message: string }, formData: FormData) => {
  try {
    await new Promise(resolve => setTimeout(resolve, 2500));

    const formInfo = Object.entries(formData);

    console.log({ formInfo });

    // revalidatePath("/contac");

    return { message: "message" };
  } catch (error) {
    return { message: "Error" };
  }
};


and below is component code:

const SubmitButton: FC = () => {
  const { pending } = useFormStatus();

  return <CustomButton rightBtnInfo="send now" rightBtnClass="text-base sm:text-lg uppercase" type={pending ? "button" : "submit"} disabled={pending} />;
};

const ContactForm: FC = () => {
  const [state, formAction] = useActionState(sendContactEmail, initialState);

  return (
    <div className="rounded-2xl bg-lightCardContainerColor p-6 shadow-2xl ring-lightBorderDividerColor dark:bg-darkCardContainerColor dark:ring-darkBorderDividerColor">
      <div>
        <SubTitle label="have any questions?" />

        <HeadingText headingLabel="drop us a line" headingVariant="h2" headingClass="text-3xl sm:text-4xl md:text-5xl capitalize py-2" />

        <HRDivider />
      </div>

      <div>
        <form action={formAction}>
          <label htmlFor="fName">Full Name</label>
          <input type="text" name="fName" id="fName" required />

          <SubmitButton />

          <p className="sr-only" aria-live="polite" role="status">
            {state.message}
          </p>
        </form>
      </div>
    </div>
  );
};

export default ContactForm;
@Singapura ts "use server"; import { revalidatePath } from "next/cache"; export const sendContactEmail = async (_state: { message: string }, formData: FormData) => { try { await new Promise(resolve => setTimeout(resolve, 2500)); const formInfo = Object.entries(formData); console.log({ formInfo }); // revalidatePath("/contac"); return { message: "message" }; } catch (error) { return { message: "Error" }; } }; and below is component code: tsx const SubmitButton: FC = () => { const { pending } = useFormStatus(); return <CustomButton rightBtnInfo="send now" rightBtnClass="text-base sm:text-lg uppercase" type={pending ? "button" : "submit"} disabled={pending} />; }; const ContactForm: FC = () => { const [state, formAction] = useActionState(sendContactEmail, initialState); return ( <div className="rounded-2xl bg-lightCardContainerColor p-6 shadow-2xl ring-lightBorderDividerColor dark:bg-darkCardContainerColor dark:ring-darkBorderDividerColor"> <div> <SubTitle label="have any questions?" /> <HeadingText headingLabel="drop us a line" headingVariant="h2" headingClass="text-3xl sm:text-4xl md:text-5xl capitalize py-2" /> <HRDivider /> </div> <div> <form action={formAction}> <label htmlFor="fName">Full Name</label> <input type="text" name="fName" id="fName" required /> <SubmitButton /> <p className="sr-only" aria-live="polite" role="status"> {state.message} </p> </form> </div> </div> ); }; export default ContactForm;
I seems to be a client component, as i thought it is simpler than it looks.

What happens is that the browser that has its default behavior with the forms (you know, submit -> restart page -> complete). The thing is that in a client component you should take care of avoiding its behavior by grabbing the event of the form and using event.preventDefault() to avoid the default behavior.

Here is a example of how to approach it using actions on a client-side form:

const ExampleForm = () => {
  const [state, formAction] = useActionState(sendContactEmail, { message: '' );


  const handleSubmit = async (e: React.FormEevent) => {
    e.preventDefault();
    // your logic here...
    startTransition(async() => {
       formAction(...);
    });
  }
  return (
    <form onSubmit={handleSubmit}>
      <label>Name here</label>
      <input type="text" name="name" id="name" />
      <label>Password here</label>
      <input type="password" name="password" id="password" />
      {state && (<p>{state}</p>) }
    </form>
  )
}
if it is not helpful please mention it.
@Losti! I seems to be a client component, as i thought it is simpler than it looks. What happens is that the browser that has its default behavior with the forms (you know, submit -> restart page -> complete). The thing is that in a client component you should take care of avoiding its behavior by grabbing the `event` of the form and using `event.preventDefault()` to avoid the default behavior. Here is a example of how to approach it using actions on a client-side form: tsx const ExampleForm = () => { const [state, formAction] = useActionState(sendContactEmail, { message: '' ); const handleSubmit = async (e: React.FormEevent) => { e.preventDefault(); // your logic here... startTransition(async() => { formAction(...); }); } return ( <form onSubmit={handleSubmit}> <label>Name here</label> <input type="text" name="name" id="name" /> <label>Password here</label> <input type="password" name="password" id="password" /> {state && (<p>{state}</p>) } </form> ) }
This should not be necessary since, the action prop in a form (<form action={formAction}>) handles that for you under the hood: it prevents the browser default behavior, runs inside a transition, and refreshes the form fields once the action completes.

@Singapura, What I’m seeing is that you’re not marking the component with “use client” so it’s doing the fallback to the default behavior to be compatible Server Side, this will work because it’s progressively enhanced to work both in the Server and the Client, but the experience is way smoother if you’re on the client.
Oh thx, i'll check it out.
SingapuraOP
any help?
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from 'next/navigation'

export const sendContactEmail = async (_state: { message: string }, formData: FormData) => {
  try {
    await new Promise(resolve => setTimeout(resolve, 2500));

    const formInfo = Object.entries(formData);

    console.log({ formInfo });

    revalidatePath("/contac"); // Uncomment this line

    return { message: "message" };
  } catch (error) {
    return { message: "Error" };
  }
};
Can you try this and see if it works?
Asian black bear
No, because server actions don't have access to the request object.
@Asian black bear No, because server actions don't have access to the request object.
SingapuraOP
so if I need to access the requst body, then how does it'll be
Asian black bear
The FormData object is the request body.
@Asian black bear The FormData object is the request body.
SingapuraOP
Understood!.. now I'll modify the code based on my requirement