Learning vue3, trying to create a form that takes multiple images as input for submission, but I keep getting these errors/warnings:
chunk-HSMNR5CP.js?v=f7f38f4d:2113 [Vue warn]: Invalid prop: type check failed for prop "modelValue". Expected String | Number, got Array
at <Input type="file" accept="image/*" multiple="multiple" ... >
at <PrimitiveSlot id="radix-v-7-form-item" aria-describedby="radix-v-7-form-item-description" aria-invalid=false >
at <FormControl >
at <FormItem >
at <Field name="photos" >
at <NewLogForm >
at <HomeView onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy(Object) {__v_skip: true} > >
at <RouterView >
at <App>
Global error: InvalidStateError: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.
My code:
<script setup lang="ts">
import { Button } from '@/components/ui/button';
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { toTypedSchema } from '@vee-validate/zod';
import { useForm } from 'vee-validate';
import { ref } from 'vue';
import * as z from 'zod';
const image_list = ref([]);
const preview_list = ref([]);
const formSchema = toTypedSchema(z.object({
title: z
.string({
required_error: 'Title is required.',
})
.min(2, {
message: 'Title must be at least 2 characters.',
}),
photos: z.array(z.instanceof(File).refine((file) => file.size < 3000000, {
message: 'Your file size must be less than 3 MB.',
})).optional(),
}));
function deleteImageFromMultiple(index: any) {
if (index > -1) {
image_list.value.splice(index, 1);
preview_list.value.splice(index, 1);
}
}
function previewMultiImage(event: any) {
reset();
console.log(event);
const input = event.target;
let count = input.files.length;
let index = 0;
if (input.files) {
while (count--) {
const reader = new FileReader();
reader.onload = (e) => {
preview_list.value.push(e.target!.result);
};
image_list.value.push(input.files[index]);
reader.readAsDataURL(input.files[index]);
index++;
}
}
}
function reset() {
image_list.value = [];
preview_list.value = [];
}
const { handleSubmit } = useForm({
validationSchema: formSchema,
});
const onSubmit = handleSubmit((values) => {
console.log('Form submitted!', values);
});
</script>
<template>
<form class="w-2/3 space-y-6" @submit="onSubmit">
<!-- <form class="w-2/3 space-y-6" @submit.prevent="onSubmit"> -->
<FormField v-slot="{ componentField }" name="title">
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input type="text" placeholder="asd" v-bind="componentField" />
</FormControl>
<FormDescription>
This is the title you will give this entry.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="photos">
<FormItem>
<FormLabel>Photos</FormLabel>
<FormControl>
<Input type="file" accept="image/*" multiple="multiple" @change="previewMultiImage"
v-bind="componentField" />
</FormControl>
<p>Preview Here:</p>
<template v-if="preview_list.length">
<div v-for="item, index in preview_list" :key="index">
<img :src="item" class="img-fluid" />
<p class="mb-0">file name: {{ image_list[index]!.name }}</p>
<p>size: {{ image_list[index].size / 1024 }}KB</p>
<button type="button" @click="deleteImageFromMultiple(index)">Delete This
Image</button>
</div>
</template>
</FormItem>
</FormField>
<Button type="submit">
Submit
</Button>
</form>
</template>
How do I fix these errors? I'm not too sure how the {componentField}
vslot/vbind works. I tried changing a few things related to the FieldSlotProps
in my template but that didn't seem to work either. If I don't add the componentField
in vslot, or put v-bind="{componentField}"
, photos will be undefined.
FieldSlotProps
interface in vee-validate.d.ts
:
interface FieldSlotProps<TValue = unknown> extends Pick<FieldContext, 'validate' | 'resetField' | 'handleChange' | 'handleReset' | 'handleBlur' | 'setTouched' | 'setErrors' | 'setValue'> {
field: FieldBindingObject<TValue>;
componentField: ComponentFieldBindingObject<TValue>;
value: TValue;
meta: FieldMeta<TValue>;
errors: string[];
errorMessage: string | undefined;
handleInput: FieldContext['handleChange'];
}
Solution:
<FormField v-slot="{ handleChange }" name="photos">
<Input
type="file"
accept="image/*"
multiple="multiple"
@change="(event) => {
handleChange(event);
previewMultiImage(event);
}"
/>
</FormField>
file input does not support v-model, because you cannot force the user to select a file on disk. When you use v-bind componentField, it tries to build a two-way binding because of prop modelValue
in componentField
.
And vee-validate document has some explanations https://vee-validate.logaretm.com/v4/api/field/
Another suggestion: you can watch values
from useForm
to achieve previewing.
const { values } = useForm()
watch(values.photos, (photos) => {
console.log('handle photos changes');
})