mon*_*ype 6 file-upload reactjs react-hook-form zod
以支持添加和编辑产品的形式处理图像上传的推荐方法是什么?这是我当前的实现,有两个选项,有什么改进建议吗?
这是当图像转换为 File 对象并将其设置为表单中图像的默认值时的第一个选项:
const getFileFromUrl = async (url: string) => {
const res = await fetch(url);
const blob = await res.blob();
return new File([blob], 'image', { type: blob.type });
};
const productForm1Schema = z.object({
name: z.string().min(1, { message: 'Name is required' }),
image: z
.custom<File>((v) => v instanceof File, {
message: 'Image is required',
})
});
export type ProductForm1Values = z.infer<typeof productForm1Schema>;
interface ProductForm1Props {
product?: Product;
}
export const ProductForm1 = ({ product }: ProductForm1Props) => {
const isAddMode = !product;
const {
register,
handleSubmit,
control,
watch,
reset,
formState: { errors, isSubmitting, isDirty },
} = useForm<ProductForm1Values>({
resolver: zodResolver(productForm1Schema),
defaultValues: product
? async () => ({
name: product.name,
image: await getFileFromUrl(product.image),
})
: {
name: '',
image: undefined,
},
});
const image = watch('image');
const imagePreview = image ? URL.createObjectURL(image) : null;
// revoke object URL to avoid memory leaks
useEffect(() => {
return () => {
if (imagePreview) URL.revokeObjectURL(imagePreview);
};
}, [imagePreview]);
const onSubmitHandler = async (data: ProductForm1Values) => {
console.log(data);
// build FormData for uploading image
const formData = new FormData();
formData.append('file', data.image);
// mock upload image to server to get image url
const imageUrl = await new Promise<string>((resolve) => {
setTimeout(() => {
resolve('https://via.placeholder.com/150');
}, 1000);
});
if (isAddMode) {
// create product
console.log({ ...data, image: imageUrl });
} else {
// update product
console.log({ id: product!.id, ...data, image: imageUrl });
}
reset();
};
return (
<form onSubmit={handleSubmit(onSubmitHandler)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
<Controller
name="image"
control={control}
render={({ field: { ref, name, onBlur, onChange } }) => (
<input
type="file"
ref={ref}
name={name}
onBlur={onBlur}
onChange={(e) => onChange(e.target.files?.[0])}
/>
)}
/>
{imagePreview && <img src={imagePreview} alt="preview" />}
{errors.image && <span>{errors.image.message}</span>}
<button type="submit" disabled={(!isAddMode && !isDirty) || isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
};
Run Code Online (Sandbox Code Playgroud)
或者
这是我创建两个用于创建和更新产品的架构时的第二个选项,并且在更新架构中图像是可选的:
const createProductSchema = z.object({
name: z.string().min(1, { message: 'Name is required' }),
image: z
.custom<File>((v) => v instanceof File, {
message: 'Image is required',
})
});
const updateProductSchema = createProductSchema.extend({
image: createProductSchema.shape.image.optional(),
});
export type ProductForm2Values =
| z.infer<typeof createProductSchema>
| z.infer<typeof updateProductSchema>;
interface ProductForm2Props {
product?: Product;
}
export const ProductForm2 = ({ product }: ProductForm2Props) => {
const [imagePreview, setImagePreview] = useState<string | null>(
product ? product.image : null,
);
const isAddMode = !product;
const {
register,
handleSubmit,
control,
reset,
formState: { errors, isSubmitting, isDirty },
} = useForm<ProductForm2Values>({
resolver: zodResolver(
isAddMode ? createProductSchema : updateProductSchema,
),
defaultValues: {
name: product?.name ?? '',
image: undefined,
},
});
// revoke object URL to avoid memory leaks
useEffect(() => {
return () => {
if (imagePreview) URL.revokeObjectURL(imagePreview);
};
}, [imagePreview]);
const onSubmitHandler = async (data: ProductForm2Values) => {
console.log(data);
let imageUrl: string | undefined;
if (data.image) {
// build FormData for uploading image
const formData = new FormData();
formData.append('file', data.image);
// mock upload image to server to get image url
imageUrl = await new Promise<string>((resolve) => {
setTimeout(() => {
resolve('https://via.placeholder.com/150');
}, 1000);
});
}
if (isAddMode) {
// create product
console.log({ ...data, image: imageUrl! });
} else {
// update product
console.log({ id: product!.id, ...data, image: imageUrl });
}
reset();
setImagePreview(product?.image ?? null);
};
return (
<form onSubmit={handleSubmit(onSubmitHandler)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
<Controller
name="image"
control={control}
render={({ field: { ref, name, onBlur, onChange } }) => (
<input
type="file"
ref={ref}
name={name}
onBlur={onBlur}
onChange={(e) => {
const file = e.target.files?.[0];
onChange(e.target.files?.[0]);
setImagePreview(file ? URL.createObjectURL(file) : null);
}}
/>
)}
/>
{imagePreview && <img src={imagePreview} alt="preview" />}
{errors.image && <span>{errors.image.message}</span>}
<button type="submit" disabled={(!isAddMode && !isDirty) || isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
};
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4383 次 |
| 最近记录: |