I am trying to accept two fields [{fileName: string, file: File}]
in a React-based Typescript Dynamic form, where the user can add multiple sets of fields [{fileName: string, file: File}]
on successful ZOD validation. Below are the codes:
Type:
interface MultipleFileType {
info: [{
fileName: string;
file: File
}]
}
Validation Schema
const multipleSchema = z.object({
fileName: z.string({ required_error: "Name is required" }).min(5, { message: "Minimum length is 5" }),
file: z
.any()
.refine((files) => files?.length == 1, 'Image is required.')
.refine((files) => files?.[0]?.size <= (1024 * 1024 * 2.5), `Max file size is 2.5 MB.`)
.refine(
(files) => ['image/jpeg', 'image/jpg', 'image/png'].includes(files?.[0]?.type),
'.jpg, .jpeg, .png and .webp files are accepted.',
),
})
export const multipleFileUploaderSchema = z.object({
info: z.array(
multipleSchema
)
})
Form:
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{fields.map((item, index) => (
<div key={item.id}>
<div className="md:flex md:flex-col">
<FormField
key={index}
control={form.control}
name={`info.${index}.fileName`} <------- ERROR 1
render={({ field }) => (
<FormItem>
<FormLabel>Filename</FormLabel>
<FormControl>
<Input type="text" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="md:flex md:flex-col">
<FormField
key={index}
control={form.control}
name={`info.${index}.file`} <------- ERROR 2
render={({ field: { onChange, ...fieldProps } }) => (
<FormItem>
<FormLabel>Picture</FormLabel>
<FormControl>
<input
type="file"
{...fieldProps}
value={""}
onChange={(event) => {
const { files } = getImageData(event, index);
// setPreviewUrl(displayUrl);
onChange(files);
}}
ref={imgInputRef}
hidden
/>
</FormControl>
{/* <FormMessage /> */}
</FormItem>
)}
/>
</div>
<Button
disabled={fields.length === 1}
className="text-red-500"
variant="outline"
size="icon"
type="button"
onClick={() => remove(index)}
>
<Trash className="h-4 w-4" />
</Button>
<Button
disabled={fields.length > 2.5 * 1024 * 1024 || !form.formState.isValid}
className="ml-4"
variant="outline"
size="icon"
type="button"
onClick={() =>
append({
fileName: "",
file: new File([], "/vite.svg"),
})
}
>
<Plus className="h-4 w-4" />
</Button>
</div>
))}
<Button type="submit" className="ml-4">
Submit
</Button>
</form>
</Form>;
The ERROR occurred in the both the markings ERROR 1 and ERROR 2 is as follows:
Type '
info.${number}.fileName' is not assignable to type '"info" | "info.0" | "info.0.fileName" | "info.0.file"'.ts(2322) controller.d.ts(20, 5): The expected type comes from property 'name' which is declared here on type 'IntrinsicAttributes & { render: ({ field, fieldState, formState, }: { field: ControllerRenderProps<MultipleFileType, "info" | "info.0" | "info.0.fileName" | "info.0.file">; fieldState: ControllerFieldState; formState: UseFormStateReturn<...>; }) => ReactElement<...>; } & UseControllerProps<...>'
Any suggestions would help.
You can always cast those name properties to a Path<z.infer<typeof yourSchemaHere>>
type, where Path
is exported from react-hook-form, although the way your types are structured you're telling typescript that the array will always have a length of 1, and with the code that you shared it's hard to tell if that is the same index that you're mapping over. If you do not want that array to always have a length of 1, change this:
interface MultipleFileType {
info: [{
fileName: string;
file: File
}]
}
export const multipleFileUploaderSchema = z.object({
info: z.array(
multipleSchema
)
})
to this:
interface MultipleFileType {
info: {
fileName: string;
file: File
}[]
}
export const multipleFileUploaderSchema = z.object({
info: multipleSchema.array()
})