使用 zod 验证 type="file"
的 shadcn Input
时,最重要的就是给 Input
指定 value=""
,让这个 Input
从一个受控组件变为非受控组件,再通过 onChange
把 File
或 FileList
对象赋值给对应的 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>