下一个 JS,表单字段验证错误时未触发提交函数

Next JS, Submit Function Not Triggered on Form Field Validation Error

提问人:Abdelghani Rizki 提问时间:11/8/2023 更新时间:11/8/2023 访问量:10

问:

我这个组件有一个问题:当其中一个表单字段出现错误时,“onSubmit”函数不会执行或被调用:

"use client";
import * as z from "zod";
import axios from "axios";
import { useState, useRef, useEffect } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";

import { CaretSortIcon } from "@/components/SVGs";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
} from "@/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { Customer } from "@/types";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react";
import { countrys } from "@/components/countries";
import { Textarea } from "@/components/ui/textarea";
import useShoppingCart from "@/hooks/useShoppingCart";
import { Checkbox } from "@/components/ui/checkbox";

const formSchema = z.object({
  billing_first_name: z
    .string()
    .min(2, { message: "Ce champ est obligatoire" }),
  billing_last_name: z.string().min(2, { message: "Ce champ est obligatoire" }),
  billing_company: z.string().optional(),
  billing_address_1: z.string().min(5, { message: "Ce champ est obligatoire" }),
  billing_address_2: z.string().optional(),
  billing_city: z.string().min(2, { message: "Ce champ est obligatoire" }),
  billing_postcode: z.string().min(3, { message: "Ce champ est obligatoire" }),
  billing_country: z.string().min(2, { message: "Ce champ est obligatoire" }),
  billing_email: z
    .string()
    .email({ message: "adresse e-mail invalide" })
    .min(3, { message: "Ce champ est obligatoire" }),
  billing_phone: z.string().min(2, { message: "Ce champ est obligatoire" }),

  shipping_first_name: z
    .string()
    .min(2, { message: "Ce champ est obligatoire" }),
  shipping_last_name: z
    .string()
    .min(2, { message: "Ce champ est obligatoire" }),
  shipping_company: z.string().optional(),
  shipping_address_1: z
    .string()
    .min(5, { message: "Ce champ est obligatoire" }),
  shipping_address_2: z.string().optional(),
  shipping_city: z.string().min(2, { message: "Ce champ est obligatoire" }),
  shipping_postcode: z.string().min(3, { message: "Ce champ est obligatoire" }),
  shipping_country: z.string().min(2, { message: "Ce champ est obligatoire" }),

  notes: z.string().optional(),
});

type CheckoutValues = z.infer<typeof formSchema>;

interface CheckoutProps {
  initialData: Customer;
}

const CheckoutForm: React.FC<CheckoutProps> = ({ initialData }) => {
  const hiddenButtonRef = useRef<HTMLButtonElement | null>(null);
  const [open_1, setOpen_1] = useState(false);
  const [open_2, setOpen_2] = useState(false);
  const [show, setShow] = useState(false);

  const items = useShoppingCart((state) => state.items);
  const shipping_metod = useShoppingCart((state) => state.shipping_method);
  const tva_cost = useShoppingCart((state) => state.tva_cost);
  const tva_rates = useShoppingCart((state) => state.tva_rates);

  const submit = useShoppingCart((state) => state.submitCheckout);
  const setSubmit = useShoppingCart((state) => state.setSubmitCheckout);
  const submitSamuray = useShoppingCart((state) => state.submit);
  const setSubmitSamuray = useShoppingCart((state) => state.setSubmit);

  useEffect(() => {
    if (hiddenButtonRef.current && submit === true) {
      hiddenButtonRef.current.click();
    }
  }, [submit]);

  const handleCheckboxChange = () => {
    setShow(!show);
  };

  let updatedDefaultValues = {
    billing_first_name: "",
    billing_last_name: "",
    billing_company: "",
    billing_address_1: "",
    billing_address_2: "",
    billing_city: "",
    billing_postcode: "",
    billing_country: "",
    billing_email: "",
    billing_phone: "",

    shipping_first_name: "",
    shipping_last_name: "",
    shipping_company: "",
    shipping_address_1: "",
    shipping_address_2: "",
    shipping_city: "",
    shipping_postcode: "",
    shipping_country: "",
    notes: "",
  };

  const form = useForm<CheckoutValues>({
    resolver: zodResolver(formSchema),
    defaultValues: updatedDefaultValues,
  });

  useEffect(() => {
    // eslint-disable-next-line
    updatedDefaultValues = initialData
      ? {
          // Billing information
          billing_first_name: initialData.billing.first_name,
          billing_last_name: initialData.billing.last_name,
          billing_company: initialData.billing.company,
          billing_address_1: initialData.billing.address_1,
          billing_address_2: initialData.billing.address_2,
          billing_city: initialData.billing.city,
          billing_postcode: initialData.billing.postcode,
          billing_country: initialData.billing.country,
          billing_email: initialData.billing.email,
          billing_phone: initialData.billing.phone,

          // Shipping information
          shipping_first_name: show
            ? initialData.shipping.first_name
            : initialData.billing.first_name,
          shipping_last_name: show
            ? initialData.shipping.last_name
            : initialData.billing.last_name,
          shipping_company: show
            ? initialData.shipping.company
            : initialData.billing.company,
          shipping_address_1: show
            ? initialData.shipping.address_1
            : initialData.billing.address_1,
          shipping_address_2: show
            ? initialData.shipping.address_2
            : initialData.shipping.address_2,
          shipping_city: show
            ? initialData.shipping.city
            : initialData.billing.city,
          shipping_postcode: show
            ? initialData.shipping.postcode
            : initialData.billing.postcode,
          shipping_country: show
            ? initialData.shipping.country
            : initialData.billing.country,
          notes: "",
        }
      : {
          billing_first_name: "",
          billing_last_name: "",
          billing_company: "",
          billing_address_1: "",
          billing_address_2: "",
          billing_city: "",
          billing_postcode: "",
          billing_country: "",
          billing_email: "",
          billing_phone: "",

          shipping_first_name: "",
          shipping_last_name: "",
          shipping_company: "",
          shipping_address_1: "",
          shipping_address_2: "",
          shipping_city: "",
          shipping_postcode: "",
          shipping_country: "",
          notes: "",
        };
    // console.log(
    //   `show: ${show}: ` + JSON.stringify(updatedDefaultValues, null, 4)
    // );
    form.reset(updatedDefaultValues);
  }, [show, initialData]);

  const onSubmit = async (data: CheckoutValues) => {
    try {
      // alert(JSON.stringify(data, null, 4));
      const response = await axios.post("/api/payment", {
        products: items,
        checkout: data,
        shipping_metod: shipping_metod,
        tva_rates: tva_rates,
        tva_cost: tva_cost,
      });
      window.location = response.data.url;
      setSubmit(false);
    } catch (error: any) {
      toast.error(error.response?.data?.error || "An error occurred.");
    } finally {
      setSubmit(false);
    }
  };

  return (
    <>
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className="flex flex-col text-left w-full gap-y-8"
        >
          <div className="bg-white shadow-md flex flex-col text-left w-full gap-y-6 p-[24px_22px] border border-solid border-[#f5f4f7] rounded-lg">
            <h4 className="text-black font-medium text-[16px] leading-[100%] tracking-[-0.04em] mb-[26px]">
              Détails de facturation.
            </h4>

            <div className="w-full grid-cols-1 grid lg:grid-cols-2 gap-8">
              {/* first Name */}
              <FormField
                control={form.control}
                name="billing_first_name"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Prénom*</FormLabel>
                    <FormControl>
                      <Input
                        disabled={submit ? true : false}
                        placeholder="Entrez votre prénom..."
                        {...field}
                        type="text"
                        className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                      />
                    </FormControl>
                    <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                  </FormItem>
                )}
              />

              {/* last Name */}
              <FormField
                control={form.control}
                name="billing_last_name"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Nom de famille*</FormLabel>
                    <FormControl>
                      <Input
                        disabled={submit ? true : false}
                        placeholder="Entrez votre nom de famille..."
                        {...field}
                        type="text"
                        className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                      />
                    </FormControl>
                    <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                  </FormItem>
                )}
              />
            </div>

            {/* Company name (optional) */}
            <FormField
              control={form.control}
              name="billing_company"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Nom de l&apos;entreprise (facultatif)</FormLabel>
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder=""
                      {...field}
                      type="text"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Country */}
            <FormField
              control={form.control}
              name="billing_country"
              render={({ field }) => (
                <FormItem className="flex flex-col ">
                  <FormLabel>Pays / Région *</FormLabel>
                  <Popover open={open_1} onOpenChange={setOpen_1}>
                    <PopoverTrigger asChild>
                      <FormControl>
                        <Button
                          disabled={submit ? true : false}
                          variant="outline"
                          role="combobox"
                          aria-expanded={open_1}
                          className={cn(
                            "w-[100%] justify-between bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]",
                            !field.value && "text-muted-foreground"
                          )}
                        >
                          {field.value
                            ? countrys.find(
                                (country) => country.value === field.value
                              )?.label
                            : "Sélectionner un pays / une région..."}
                          <CaretSortIcon />
                        </Button>
                      </FormControl>
                    </PopoverTrigger>
                    <PopoverContent className="w-[200px] max-h-96 overflow-y-auto p-0 relative">
                      <Command>
                        <CommandInput
                          placeholder="Search country..."
                          className="h-9"
                        />
                        <CommandEmpty>No country found.</CommandEmpty>
                        <CommandGroup>
                          {countrys.map((country) => (
                            <CommandItem
                              value={country.label}
                              key={country.value}
                              onSelect={() => {
                                form.setValue("billing_country", country.value);
                                setOpen_1(false);
                              }}
                            >
                              {country.label}
                              <CheckIcon
                                className={cn(
                                  "ml-auto h-4 w-4",
                                  country.value === field.value
                                    ? "opacity-100"
                                    : "opacity-0"
                                )}
                              />
                            </CommandItem>
                          ))}
                        </CommandGroup>
                      </Command>
                    </PopoverContent>
                  </Popover>

                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Town / City * */}
            <FormField
              control={form.control}
              name="billing_city"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Ville *</FormLabel>
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder="Entrez votre ville"
                      {...field}
                      type="text"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Street address */}
            <FormField
              control={form.control}
              name="billing_address_1"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Adresse postale *</FormLabel>
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder="Numéro de maison et nom de la rue"
                      {...field}
                      type="text"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Street address */}
            <FormField
              control={form.control}
              name="billing_address_2"
              render={({ field }) => (
                <FormItem>
                  {/* <FormLabel>Street address *</FormLabel> */}
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder="Appartement, suite, unité, etc. (facultatif)"
                      {...field}
                      type="text"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Postcode / ZIP * */}
            <FormField
              control={form.control}
              name="billing_postcode"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Code postal / ZIP *</FormLabel>
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder="Entrez la code postal de votre ville"
                      {...field}
                      type="text"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Phone */}
            <FormField
              control={form.control}
              name="billing_phone"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Téléphone *</FormLabel>
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder="Entrez votre téléphone"
                      {...field}
                      type="text"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />

            {/* Email address */}
            <FormField
              control={form.control}
              name="billing_email"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Adresse e-mail *</FormLabel>
                  <FormControl>
                    <Input
                      disabled={submit ? true : false}
                      placeholder="Entrez votre adresse e-mail"
                      {...field}
                      type="email"
                      className="bg-[#f1f1f1] max-w-[calc(100%-12px)] mx-[auto] focus-visible:outline-[#dfb572]"
                    />
                  </FormControl>
                  <FormMessage className="text-[#ea400e] text-[10px] p-[5px_0px_0px] !m-0" />
                </FormItem>
              )}
            />
          </div>

          <Button type="submit" className={"hidden"} ref={hiddenButtonRef}>
            Continuer vers le paiement
          </Button>
        </form>
      </Form>
    </>
  );
};`

export default CheckoutForm;

我希望当表单提交时,onSubmit 将被调用,如果表单上有任何错误,将执行“setSubmit(false)”,而无需向 api 发送任何请求

reactjs 表单 验证 next.js react-hook-form

评论


答: 暂无答案