Replies: 2 comments
-
It's not pretty, but here is a working example with server actions, client/server Zod validation, and loading state using "use client";
import { LoadingIcon } from "@/components/images/icons";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { routes } from "@/config/routes";
import { getSchemaDefaults } from "@/lib/utils/get-schema-defaults";
import { userApplySchema } from "@/schemas/user";
import { type ActionState } from "@/server/actions/middleware";
import { cn } from "@/lib/utils";
import { createVendorAction } from "@/server/actions/users";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowRightIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import {
useActionState,
useCallback,
useEffect,
useRef,
useTransition,
type HTMLAttributes,
} from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { type z } from "zod";
type ComponentProps = HTMLAttributes<HTMLFormElement>;
export function VendorSignUpForm(props: ComponentProps) {
const router = useRouter();
const formRef = useRef<HTMLFormElement>(null);
const form = useForm({
resolver: zodResolver(userApplySchema),
defaultValues: getSchemaDefaults(userApplySchema),
});
const [state, action, pending] = useActionState<ActionState, FormData>(createVendorAction, {
error: "",
success: "",
});
const [, startTransition] = useTransition();
const onSubmit = useCallback(
async (_values: z.infer<typeof userApplySchema>) => {
startTransition(() => {
action(new FormData(formRef.current!));
});
},
[action],
);
useEffect(() => {
if (pending || !state) {
return;
}
if (state.success) {
// Successfully submitted the form
toast.success(state.success);
router.push(routes.vendor.onboarding.welcome);
} else if (state.error) {
if (typeof state.error === "string") {
// There was a server-side error
toast.error(state.error);
} else if (form) {
// There were server-side form errors
setFormErrors({ form, errors: state.error });
}
}
}, [state, router, form, pending]);
return (
<Form {...form}>
<form
{...props}
ref={formRef}
action={action}
onSubmit={form.handleSubmit(onSubmit)}
className={cn("grid grid-cols-2 items-start gap-6", props.className)}
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input placeholder="Email Address" type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="col-span-2 flex gap-xs bg-black text-white"
disabled={pending}
>
Register
{pending ? <LoadingIcon /> : <ArrowRightIcon className="size-4" />}
</Button>
</form>
</Form>
);
} |
Beta Was this translation helpful? Give feedback.
-
Wondering why do you want/insist to use the useFormState(), as far I understand it's purpose is for plain forms without any lib like react-hook-form. I mean what you gain by it then just using useTtransition() ans wrapping the onSubmit() function (it can call inside server-action, fetch or axios,...) - you still have is isPending state from the useTtransition(). Why overengineer this |
Beta Was this translation helpful? Give feedback.
-
Hi, I was just wondering if the Form component will be redesigned to use the new React hook like
useFormState
?Or do you have an example of Shadcn Form properly integrating server action, server and client side Validation, and loading state using useFormState?
Beta Was this translation helpful? Give feedback.
All reactions