shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
62.84k stars 3.53k forks source link

Server Actions in Form Component #1312

Closed nilaq closed 10 months ago

nilaq commented 10 months ago

I am trying to build a simple signup dialog, where users can leave their email to be put on a waitlist. However, I am running into issues when trying to trigger a Next.js Server Action as submit action in the Form Component. Is this a bug or am i just doing something wrong?

page.tsx

export default function Home() {
  return (
      <main>
         <GetStartedButton/>
      </main>
  );
}

GetStartedButton.tsx

"use client";
import React from "react";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import {
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { formSchema, onFormSubmit } from "./actions";

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

export default const GetStartedButton = () => {

  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button className="text-base font-normal ">Get Started</Button>
      </DialogTrigger>
      <DialogContent className="sm:max-w-[425px]">
      <DialogHeader>
        <DialogTitle>Sign up for the waitlist</DialogTitle>
        <DialogDescription>
          We are currently in private beta. Sign up to get early access.
        </DialogDescription>
      </DialogHeader>
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onFormSubmit)} className="space-y-8">
          <FormField
            control={form.control}
            name="email"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Email</FormLabel>;
                <FormControl>
                  <Input placeholder="steve@apple.com" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <DialogFooter>
            <Button type="submit">Join Waitlist</Button>
          </DialogFooter>
        </form>
      </Form>
    </DialogContent>
    </Dialog>
  );
};

actions.ts

"use server";
import * as z from "zod";
import { db } from "@/db";
import { waitlist } from "@/db/schema";

export const formSchema = z.object({
  email: z.string().email(),
});

export async function onFormSubmit(values: z.infer<typeof formSchema>) {
  "use server";
  const update = await db.insert(waitlist).values({ email: "test@test.de" });
}

I am getting this error:

Uncaught (in promise) TypeError: r[(intermediate value)(intermediate value)(intermediate value)] is not a function
    at eval (webpack-internal:///(:3000/app-pages-browser)/./node_modules/@hookform/resolvers/zod/dist/zod.mjs:7:671)
    at Object.eval [as resolver] (webpack-internal:///(:3000/app-pages-browser)/./node_modules/@hookform/resolvers/zod/dist/zod.mjs:7:901)
    at _executeSchema (webpack-internal:///(:3000/app-pages-browser)/./node_modules/react-hook-form/dist/index.esm.mjs:1707:53)
    at eval (webpack-internal:///(:3000/app-pages-browser)/./node_modules/react-hook-form/dist/index.esm.mjs:2128:46)
    at HTMLUnknownElement.callCallback (webpack-internal:///(:3000/app-pages-browser)/./node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js:20207:14)
igorm84 commented 10 months ago

@nilaq it seems you not properly assigning the form props, please ref to NextJS Docs because your problem isnt from the shadcn-ui itself

ShaikRehan123 commented 10 months ago

Check out this discussion, there are possible solutions: https://github.com/react-hook-form/react-hook-form/issues/10391

nilaq commented 10 months ago

Thanks @igorm84 & @ShaikRehan123, figured it out, had indeed nothing to do with the Form component

devraj commented 8 months ago

I can across this as well, after reading those docs and the thread, I managed to get it working by calling the server function from the the handleSubmit call has a reference to:

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

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

and obviously the form is configured as per the shadcnui docs:

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

In this instance I have a server method either redirecting the user to another (on successful login) or returning a message if there was a problem (credentials didn't match):

    const { pending } = useFormStatus();

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

    async function onSubmit(values: z.infer<typeof loginFormSchema>) {
        const response = await performLogin(values);
        if(response && response.message) {
            setMessage(response.message);
        }
    }

Wonder if some of these component templates will move to using useForState that is now available in react canary.

aymanechaaba1 commented 6 months ago

Anyone implemented error handling and toast notifications with server actions?

Olivier-OG commented 5 months ago

Anyone implemented error handling and toast notifications with server actions?

Just add your toast function/error handling in the onSubmit.

onSubmit={async (data) => {
  const result = await sendEmail(data);
  if (result.error)
    return toast.error(result.message);
  toast.success(result.message);
  form.reset();
}}
alfonsocartes commented 5 months ago

I had the zod schema in a "use server" file (the one for the action). I moved it to the "use client" file where the form is, and it worked.

JMRodriguez-work commented 4 months ago

is there a way to use it with the action instead of onSubmit ? I want to use useFormStatus but it doesn't work with onSubmit

aymanechaaba1 commented 4 months ago

@JMRodriguez-work Don't use the form component by shadcn, use the native one, Checkout my project AntiExcel, I've implemented many forms with useFormStatus and useFormState

eligarlo commented 4 months ago

I had the zod schema in a "use server" file (the one for the action). I moved it to the "use client" file where the form is, and it worked.

@alfonsocartes I first moved the schema to the 'use client' and exported the schema so I could add the validation in the server too. I got an error saying I could not do that so, in the end, I created a separate .ts file (no "use client" or "use server") and from there exported my schema so I could use it in both the client and server.