代码之家  ›  专栏  ›  技术社区  ›  Sulejman

使用Shadcn、Zod和React Hook Form的Next.js 14中的图像上传和表单验证问题

  •  0
  • Sulejman  · 技术社区  · 2 年前

    我目前正在使用Shadcn、Zod和React Hook Form开发Next.js 14应用程序,并且在图像上传和表单验证方面遇到了一些问题。我有一个用于创建用户配置文件的表单,我使用createProfile函数提交表单数据,包括一个图像。然而,我遇到了以下问题:

    • 图片上传问题-Django后端返回错误消息:“提交的数据不是文件。请检查表单上的编码类型。”。
    • 用户Id问题-提交表单时,Django后端报告了一个与“用户”字段相关的错误:“无效的pk“0”-对象不存在。”我怀疑这与向Django后台发送字符串而不是数字有关。
    • 注释字段输入问题-我在代码中注释了一种处理图像输入字段的替代方法。当我在该字段上使用{…field}方法时,我的整个应用程序在上传图像时会崩溃。我不知道为什么会发生这种情况,希望能深入了解这个问题。 这是我的代码(如果太大,很抱歉,我认为最好显示整个代码) 个人资料表格.tsx
    "use client";
    
    import { zodResolver } from "@hookform/resolvers/zod";
    import { useForm } from "react-hook-form";
    import * as z from "zod";
    
    import { useGlobalContext } from "@/components/context/GlobalContext";
    
    import { toast } from "@/lib/use-toast";
    import { createProfile } from "@/lib/users/createProfile";
    
    import { Button } from "@/components/ui/button";
    import {
      Form,
      FormControl,
      FormField,
      FormItem,
      FormLabel,
      FormMessage,
    } from "@/components/ui/form";
    import { Input } from "@/components/ui/input";
    import { Label } from "@/components/ui/label";
    
    // https://github.com/colinhacks/zod/issues/387
    // https://github.com/blitz-js/blitz/discussions/2292#discussioncomment-826778
    
    const profileFormSchema = z.object({
      full_name: z.string().min(2).max(20),
      address: z.string().min(2).max(50),
      phone_number: z.string().min(2).max(20),
      picture: z
        .any()
        .refine((file) => file?.length == 1, "File is required.")
        .refine(
          (file) =>
            file[0]?.type === "image/png" ||
            file[0]?.type === "image/jpeg" ||
            file[0]?.type === "image/jpg",
          "Must be a png, jpeg or jpg.",
        )
        .refine((file) => file[0]?.size <= 5000000, `Max file size is 5MB.`),
      // picture: typeof window === "undefined" ? z.any() : z.instanceof(File),
    });
    
    export type ProfileFormSchemaType = z.infer<typeof profileFormSchema>;
    
    export default function ProfileForm() {
      const { userInfo, setUserInfo, accessToken } = useGlobalContext();
    
      const form = useForm<ProfileFormSchemaType>({
        resolver: zodResolver(profileFormSchema),
        defaultValues: {
          full_name: "",
          address: "",
          phone_number: "",
          picture: undefined,
        },
      });
    
      async function onSubmit(values: ProfileFormSchemaType) {
        const formData = new FormData();
    
        formData.append("picture", values.picture);
        formData.append("full_name", values.full_name);
        formData.append("address", values.address);
        formData.append("phone_number", values.phone_number);
        formData.append("user", String(userInfo.id));
    
        console.log(formData);
    
        try {
          const response = await createProfile(formData, accessToken);
          console.log(response);
    
          toast({
            title: "success",
          });
        } catch (error) {
          toast({
            title: "error",
            description: error?.toString(),
          });
          console.error(error);
        }
      }
    
      const fileRef = form.register("picture", { required: true });
    
      return (
        <Form {...form}>
          <form
            onSubmit={form.handleSubmit(onSubmit)}
            className="space-y-8 rounded-tr-2xl rounded-tl-2xl border p-8"
          >
            <FormField
              control={form.control}
              name="full_name"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Full name</FormLabel>
                  <FormControl>
                    <Input placeholder="John Doe" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <FormField
              control={form.control}
              name="phone_number"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Phone number</FormLabel>
                  <FormControl>
                    <Input placeholder="+38269123123" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <FormField
              control={form.control}
              name="address"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Address</FormLabel>
                  <FormControl>
                    <Input placeholder="Podgorica, Donja Gorica" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            {/* <FormField
              control={form.control}
              name="picture"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Picture</FormLabel>
                  <FormControl>
                    <Input
                      accept="image/png, image/jpeg, image/jpg"
                      type="file"
                      placeholder="profile image"
                      {...fileRef}
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            /> */}
    
            <Label htmlFor="picture">Picture</Label>
    
            <Input
              id="picture"
              type="file"
              placeholder="profile image"
              {...form.register("picture", { required: true })}
            />
            {form.formState.errors.picture && (
              <div>
                <span>This field is required</span>
              </div>
            )}
            <Button
              disabled={form.formState.isSubmitting}
              type="submit"
              className={"block mx-auto px-8"}
            >
              Submit
            </Button>
          </form>
        </Form>
      );
    }
    

    以及在onSubmit中调用的createProfile函数: createProfile.ts:

    "use server";
    
    import { env } from "@/lib/zod_schemas/envSchema";
    
    type ErrorResponse = {
      errors: ProfileErrorApiResponse;
    };
    
    export async function createProfile(
      formData: FormData,
      token: string,
    ): Promise<ProfileSuccessApiResponse | ErrorResponse> {
      let response: Response;
      try {
        response = await fetch(`${env.API_BASE_URL}/products/profile/`, {
          method: "POST",
          body: formData,
          headers: {
            // "Content-Type": "multipart/form-data",
            Authorization: `JWT ${token}`,
          },
          cache: "no-cache", // This option disables caching
        });
      } catch (error) {
        console.error("Error: ", error);
        throw new Error("Network error");
      }
    
      if (response.ok) {
        const responseData: ProfileSuccessApiResponse = await response.json();
        return responseData;
      } else if (response.status === 400) {
        const responseError: ProfileErrorApiResponse = await response.json();
        return { errors: responseError };
      } else {
        throw new Error("Unexpected error. Please try again later!");
      }
    }
    

    当我提交时,我从django后端收到了以下错误:

    {
       "errors":{
          "picture":[
             "The submitted data was not a file. Check the encoding type on the form."
          ],
          "user":[
             "Invalid pk \"0\" - object does not exist."
          ]
       }
    }
    

    谢谢你的帮助。

    0 回复  |  直到 2 年前