AberSheeran
Aber Sheeran

使用 zod 验证 Input file

起笔自
所属文集: 程序杂记
共计 2779 个字符
落笔于

使用 zod 验证 type="file" 的 shadcn Input 时,最重要的就是给 Input 指定 value="",让这个 Input 从一个受控组件变为非受控组件,再通过 onChangeFileFileList 对象赋值给对应的 form 字段。在 GitHub issue 里,缺少了 value="" 这个设置,会导致文件被选中后代码崩溃。

一个好的代码样例胜过千言万语,以下是一个使用了 shadcn 组件的头像修改表单样例,除去 zod 的验证功能外,它还可以让用户预览自己的头像显示效果。

import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import {
  FormField,
  FormItem,
  FormControl,
  FormMessage,
  Form,
  FormLabel,
} from "@/components/ui/form";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";

const formSchema = z.object({
  avatar: z
    .custom<File>()
    .refine((file) => file?.size <= 5 * 1024 * 1024, `Max file size is 5MB.`)
    .refine(
    (file) =>
        ["image/jpeg", "image/jpg", "image/png", "image/webp"].includes(
        file?.type,
        ),
    ".jpg, .jpeg, .png and .webp files are accepted.",
    )
    .optional(),
});

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

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

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="avatar"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Avatar</FormLabel>
          <FormControl>
            <div className="flex items-end gap-4">
              <Avatar className="h-28 w-28">
                <AvatarImage
                  src={((avatar) =>
                    avatar ? URL.createObjectURL(avatar) : undefined)(
                    form.getValues().avatar,
                  )}
                />
              </Avatar>
              <Button variant="outline" className="relative">
                Change Avatar
                <Input
                  className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
                  type="file"
                  accept="image/*"
                  multiple={false}
                  {...field}
                  value=""
                  onChange={(event) => {
                    form.setValue("avatar", event.target.files?.[0]);
                  }}
                />
              </Button>
            </div>
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
  </form>
</Form>
如果你觉得本文值得,不妨赏杯茶
让一份代码同时支持同步与异步
没有下一篇