ui icon indicating copy to clipboard operation
ui copied to clipboard

Page refreshes after submitting form

Open mortz123 opened this issue 2 years ago • 25 comments

I'm using react-hook-form as mentioned in shadncn-ui docs and the page keeps refreshing everytime I try to submit the form. Is there anyway I can prevent default behavior of the form ?

"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";

const FormSchema = z.object({
  username: z.string().min(2, {
    message: "Username must be at least 2 characters.",
  }),
});
export default function Home() {
  const form = useForm({
    resolver: zodResolver(FormSchema),
  });

  const onSubmit = (data) => {
    console.log(data);
  };
  return (
    <main className="flex flex-col items-center justify-between min-h-screen p-24">
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className="w-2/3 space-y-6"
        >
          <FormField
            control={form.control}
            name="username"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Username</FormLabel>
                <FormControl>
                  <Input placeholder="shadcn" {...field} />
                </FormControl>
                <FormDescription>
                  This is your public display name.
                </FormDescription>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button type="submit">Submit</Button>
        </form>
      </Form>
    </main>
  );
}

mortz123 avatar Oct 16 '23 13:10 mortz123

Are there any errors when this happens either in your server console or browser console? The shadcn components shouldn't cause a pause refresh on their own nor should react-hook-form. From the example code you included, I don't see an issue. Perhaps it is in the configuration of the framework you're using.

steveafrost avatar Oct 16 '23 16:10 steveafrost

Are there any errors when this happens either in your server console or browser console? The shadcn components shouldn't cause a pause refresh on their own nor should react-hook-form. From the example code you included, I don't see an issue. Perhaps it is in the configuration of the framework you're using.

no, no errors.. I am using Next.js v13.5.4, it should work fine, i really don't get it. Have any other ideas maybe?

mortz123 avatar Oct 17 '23 05:10 mortz123

I have the same error. After form submission, the page refreshes itself and appends URL params image

luantran1311 avatar Oct 17 '23 08:10 luantran1311

I confirm, same error...

ValiDraganescu avatar Oct 17 '23 11:10 ValiDraganescu

Try to create a reduced reproduction on CodeSandbox / JSBin / etc and see if the issue still happens. If it does, then drop it here!

steveafrost avatar Oct 17 '23 14:10 steveafrost

Yes please share a reproducible copy. Thank you.

shadcn avatar Oct 21 '23 14:10 shadcn

This is how I managed to reproduce it by using onSubmit={() => form.handleSubmit(onSubmit)}: link

AvaYnE2 avatar Oct 23 '23 18:10 AvaYnE2

You have to handle the default event from the form which is submission if you add an anonymous function like seen in the Code Sandbox included. The default behavior of a submitted form is to submit the data to the server then refresh the page.

onSubmit={(event) => {
  event.preventDefault();
  form.handleSubmit(onSubmit);
}

steveafrost avatar Oct 23 '23 19:10 steveafrost

@steveafrost doesn't react-hook-form handle e.preventDefault() by default? @AvaYnE2 you don't need the have the onsubmit as a function, the code you've commented is the correct way of handling submit.

servesh-chaturvedi avatar Oct 24 '23 13:10 servesh-chaturvedi

It will if you don't pass in an anonymous function. By passing in an anonymous function, the default behavior of a form will execute. If you pass in the react-hook-form method, then the event goes to that method and is handled by react-hook-form – I can't find it in their documentation though that's what I've observed. You can see further discussion in this issue.

Default event passed & prevented by react-hook-form's handleSubmit onSubmit={form.handleSubmit(onSubmit)}

Default event not passed to react-hook-form's handleSubmit `onSubmit={() => form.handleSubmit(onSubmit)}

Hope that helps!

This issue should be closed now. It is a non-issue and related to react-hook-form.

steveafrost avatar Oct 25 '23 19:10 steveafrost

UPDATE: this issue was related with a third-party Chrome extension. 1Password. Leaving this here so maybe it can help somebody.


Still an issue for me.

onSubmit={(event) => {
  event.preventDefault();
  form.handleSubmit(onSubmit);
}

completely prevents the submission of the form. Now:

onSubmit={form.handleSubmit(onSubmit)}

submits properly clicking on the submit button. It fails when pressing return. Really weird behavior tbh.

Screenshot 2023-11-24 at 11 46 57 PM

leonardorb avatar Nov 25 '23 04:11 leonardorb

I'm having the same issue. I'm using Astro framework. Here is my code:

import React from 'react';
import { Button } from "@/components/ui/button";
import {Input} from "@/components/ui/input.tsx";

import {

  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card.tsx";

import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

import { useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";

const formSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(32),
});


function Login() {

  const form = useForm<z.infer<typeof formSchema>>({
      resolver: zodResolver(formSchema),
      defaultValues: {
        email: "",
        password: "",
      }
  });

  function onSubmit(data: z.infer<typeof formSchema>) {
    console.log(data);
  }

  return (
    <div>
      <Card className="login-card">
        <CardHeader>
          <CardTitle>Sign In</CardTitle>
        </CardHeader>
        <CardContent>
          <Form {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
              <FormField
                control={form.control}
                name="email"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Email</FormLabel>
                    <FormControl>
                      <Input type="email" placeholder="[email protected]" {...field} />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name="password"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Password</FormLabel>
                    <FormControl>
                      <Input type="password" placeholder="********" {...field} />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <Button type="submit">Sign In</Button>
            </form>
          </Form>
        </CardContent>
      </Card>
    </div>
  );
}

export default Login;

When I submit, the page refreshes and the values entered into the form show up as URL parameters in the address bar.

r25s avatar Dec 01 '23 14:12 r25s

<Form {...form} onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
    <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">

There are two submit events going on there which is surely causing some weird behavior and possibly what you are seeing.. There should only be a submit on the native HTML element <form>.

This is different than the original issue posted here which involved an anonymous function invoking the default behavior of forms and later, possible interference from a browser extension.

steveafrost avatar Dec 01 '23 16:12 steveafrost

@steveafrost Sorry, I uploaded a bad code snippet. The dual onSubmit was me testing to see if onSubmit would work correctly on the Form component. I edited it to the correct thing.

So, without the duplicate onSubmit it gives the problem as I described. I basically just copied the example from the docs, changing the form to accept username and password instead.

Thanks for replying. I hope I can get a resolution to this.

r25s avatar Dec 01 '23 21:12 r25s

@servesh-chaturvedi My mistake, I edited it in place to correct it from earlier.

Please allow me to instead submit the very code example used in the shadcn-ui docs. I have tested it just now, getting the same results.

"use client"

import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"

import { Button } from "@/components/ui/button"
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"

const formSchema = z.object({
  username: z.string().min(2).max(50),
})

export function ProfileForm() {
  // 1. Define your form.
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      username: "",
    },
  })

  // 2. Define a submit handler.
  function onSubmit(values: z.infer<typeof formSchema>) {
    // Do something with the form values.
    // ✅ This will be type-safe and validated.
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <Input placeholder="shadcn" {...field} />
              </FormControl>
              <FormDescription>
                This is your public display name.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  )
}

As stated above, the page refreshed and the value of the input is displayed in the address bar as a URL query param: http://localhost:4321/signin?username=testuser.

(also the "use client" at the beginning doesn't actually do anything in Astro, but the code works the same with or without it).

r25s avatar Dec 02 '23 06:12 r25s

@ronmegg it would be helpful if you could set up a codesandbox demo of the issue

servesh-chaturvedi avatar Dec 02 '23 13:12 servesh-chaturvedi

I solved it in Next.js. In my case, html and body tag was missing in layout file.

umerpall avatar Jan 04 '24 16:01 umerpall

Having this exact same issue https://github.com/shadcn-ui/ui/issues/1758#issuecomment-1837065050 despite having tried different things like proposed here or for dom events in general.

darul75 avatar Jan 17 '24 15:01 darul75

function onSubmit(values: z.infer<typeof formSchema>, event: React.BaseSyntheticEvent | undefined) {
   event?.preventDefault();
   // Do something with the form values.
   // ✅ This will be type-safe and validated.
   console.log(values)
 }

@r25s that should solve it by passing an 'event' extra parameter.

note: also bumped Astro version to "^4.0.7"

darul75 avatar Jan 18 '24 10:01 darul75

Hi, I'm working on a personal project and I face the same issue. I'm using Astro, Shadcn, Tailwind, Resend, React Hook Form, TypeScript, Sanity and Zod for this one. It works fine without shadcn, once I try to use the form component, the form submit triggers a page refresh and I got the params in the url. I tried the solution provided by @darul75, it's actually included in the snippet below, but it doesn't change anything.

import { type FormEvent } from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm, type SubmitHandler } from 'react-hook-form'
import { z } from 'zod'

import { Button } from '@/components/ui/button'
import {
  Form as ShadcnForm,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Textarea } from '@/components/ui/textarea'
import { Input } from '@/components/ui/input'
import { toast } from '@/components/ui/use-toast'

export default function Form() {
  const formSchema = z.object({
    name: z.string().min(5, { message: 'Name must be at least 5 characters.' }),
    email: z.string().email({ message: 'Invalid email address' }),
    message: z
      .string()
      .min(10, {
        message: 'Bio must be at least 10 characters.',
      })
      .max(160, {
        message: 'Bio must not be longer than 30 characters.',
      }),
  })

  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: '',
      email: '',
      message: '',
    },
  })

  const onSubmit = async (
    data: z.infer<typeof formSchema>,
    event: React.BaseSyntheticEvent | undefined,
  ) => {
    event?.preventDefault()
    try {
      const res = await fetch('/api/sendEmail.json', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          from: '[email protected]',
          to: '[email protected]',
          subject: `Contact de ${data.name}, ${data.email}`,
          html: data.message,
          text: data.message,
        }),
      })
      const r = await res.json()
      console.log(r)
    } catch (error) {
      console.error(error)
    }
  }

  return (
    <ShadcnForm {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="Your name" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="Your email" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="message"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Message</FormLabel>
              <FormControl>
                <Textarea
                  placeholder="Your message"
                  className="resize-none"
                  {...field}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </ShadcnForm>
  )
}

I'm adding my package.json, in case it can help

{
  "name": "modern-portfolio",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "format": "prettier -w . --cache",
    "build": "astro check && astro build",
    "preview": "astro preview"
  },
  "dependencies": {
    "@astrojs/check": "^0.4.1",
    "@astrojs/react": "^3.0.10",
    "@astrojs/tailwind": "^5.1.0",
    "@hookform/resolvers": "^3.3.4",
    "@radix-ui/react-accordion": "^1.1.2",
    "@radix-ui/react-avatar": "^1.0.4",
    "@radix-ui/react-label": "^2.0.2",
    "@radix-ui/react-slot": "^1.0.2",
    "@radix-ui/react-toast": "^1.1.5",
    "@sanity/astro": "^2.2.0",
    "@sanity/client": "^6.12.4",
    "@sanity/image-url": "^1.0.2",
    "astro": "^4.3.1",
    "astro-portabletext": "^0.9.6",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.0",
    "groqd": "^0.15.10",
    "lucide-react": "^0.321.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.50.1",
    "resend": "^3.2.0",
    "sanity": "^3.28.0",
    "tailwind-merge": "^2.2.1",
    "tailwindcss": "^3.4.1",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.11.16",
    "@types/react": "^18.2.55",
    "@types/react-dom": "^18.2.19",
    "prettier": "^3.2.4",
    "prettier-plugin-astro": "^0.13.0",
    "prettier-plugin-tailwindcss": "^0.5.11",
    "typescript": "^5.3.3"
  }
}

Remondo02 avatar Feb 18 '24 07:02 Remondo02

@shadcn, @darul75 OK, so after some time I found out what was wrong.

In short, there were two problems: first, a bad type definition coming from an dynamic file on my side; second, an ambiguous import in the form that came from the form.tsx file of Shadcn. I had to manually add the type keyword in front of few imports (ControllerProps, FieldPath - which was the main problem -, and finally FieldValues).

With that applied, it was working properly.

Remondo02 avatar Feb 22 '24 05:02 Remondo02

On Astro, my resolution was to correctly use a client: directive on the Astro page/component which was responsible for importing the React component. The directive was missing, so my React component/layout was not hydrating, so the handleSubmit was not being called.

chrsmlls333 avatar Feb 23 '24 06:02 chrsmlls333

i just added the type on import ControllerProps, FieldPath, FieldValues, from 'react-hook-form'

import { Controller, type ControllerProps, type FieldPath, type Field Value, Form Provider, useFormContext, } from "react-hook form";

and it solved for me

dbyjar avatar Mar 21 '24 03:03 dbyjar

i just added the type on import ControllerProps, FieldPath, FieldValues, from 'react-hook-form'

import { Controller, type ControllerProps, type FieldPath, type Field Value, Form Provider, useFormContext, } from "react-hook form";

and it solved for me

I have tried this but does not work for me unfortunately.

Keeps refreshing all the time

Stefan94V avatar Mar 24 '24 14:03 Stefan94V

I had the same issue. I had 2 forms within my component tree. and outter form works well and for the inner one it will refreash the page. Just use 2 different names for forms const firstForm = useForm<FirstType>({ resolver: zodResolver(FirstShema), });

const secondForm = useForm<SecondType>({ resolver: zodResolver(SecondShema), }); Don't know exact reason. But worked for me. Hope this will help someone.

praneethwelideniya avatar Apr 02 '24 14:04 praneethwelideniya

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Jun 11 '24 23:06 shadcn

I encountered the same issue. Initially, I couldn’t submit the form, and when I was finally able to, the page started to reload and add values to the query.

Fix in my case: I was setting the default value of the form from a prop. Later, I realised that an attribute wasn’t receiving the correct value.

ImEins avatar Aug 04 '24 19:08 ImEins