Combined Approach with Unified Schema and Conditional Handling
const { data, isFetching } = useQuery(
import React from "react";
import { z } from "zod";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
const productFormSchema = z.object({
name: z.string().min(1, { message: "Name is required" }),
image: z
.union([
z.instanceof(File, { message: "Image is required" }),
z.string().optional(), // Allow the existing image URL for editing mode
])
.refine((value) => value instanceof File || typeof value === "string", {
message: "Image is required",
}),
});
export type ProductFormValues = z.infer<typeof productFormSchema>;
interface ProductForm2Props { product?: Product; }
export const ProductForm1 = ({ product }: ProductForm2Props) => {
const isAddMode = !product;
const [imagePreview, setImagePreview] = useState<string | null>(
product?.image ?? null
);
const {
register,
handleSubmit,
control,
watch,
reset,
formState: { errors, isSubmitting, isDirty },
} = useForm<ProductFormValues>({
resolver: zodResolver(productFormSchema),
defaultValues: {
name: product?.name ?? "",
image: product?.image ?? "", // Use the existing image URL for editing mode
},
});
const image = watch("image");
useEffect(() => {
if (image instanceof File) {
const imageUrl = URL.createObjectURL(image);
setImagePreview(imageUrl);
return () => URL.revokeObjectURL(imageUrl);
}
if (typeof image === "string") {
setImagePreview(image);
}
}, [image]);
const onSubmitHandler = async (data: ProductFormValues) => {
console.log(data);
let imageUrl: string | undefined;
if (data.image instanceof File) {
// 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);
});
} else {
imageUrl = data.image; // Use the existing image URL for updating mode
}
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) => {
const file = e.target.files?.[0];
onChange(file ? file : imagePreview); // Keep the existing image in edit mode
setImagePreview(
file ? URL.createObjectURL(file) : product?.image ?? 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>
);
};
This approach should simplify your codebase while retaining flexibility for future enhancements.
Work with our skilled React developers to accelerate your project and boost its performance.
Hire Reactjs Developers