2

I'm trying to build a component in VueJS with input field for file type. Here is my component code:

<template>
    <div class="flex-col justify-start w-full">
        <div class="mt-2 block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">{{ label }}</div>
        <input
            class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
            :class="errorDisplay ? 'border-red-500 focus:bg-white focus:border-red-500': ''"
            type="file"
            :value="value" @input="emitEvent($event)"
            ref="input_file"
        >
        <span v-if="hint" class="text-xs text-gray-400 font-medium">{{ hint }}</span>
        <span v-if="errorDisplay" class="text-xs text-pink-600 font-medium">{{ errorDisplay }}</span>
    </div>
</template>

<script>
    export default {
        name: "InputFile",
        props: {
            label: String,
            hint: {
                type: String,
                default: () => ''
            },
            error: {
                type: Array,
                default: () => []
            },
            placeholder: String,
            value: Object,
        },
        methods: {
            emitEvent(event) {
                var reader = new FileReader();
                reader.readAsDataURL(event.target.files[0]);
                reader.onload = () => {
                    const docs = {
                        name: event.target.files[0].name,
                        size: event.target.files[0].size,
                        lastModifiedDate: event.target.files[0].lastModifiedDate,
                        base64: reader.result
                    };
                    console.log(docs);
                    this.$emit('input', docs)
                };
            }
        },
        computed: {
            errorDisplay() {
                if(this.error.length)
                    return this.error.join(', ');
                else
                    return '';
            }
        }
    }
</script>

And I'm calling my component as below:

<template>
    <div class="flex items-center justify-start">
        <div class="w-1/2 m-2 rounded-lg shadow-lg border b-gray-400 rounded flex flex-col justify-start items-start p-6 bg-white">
            <div class="border-b -m-2 mb-3 px-6 py-2 flex-none w-full justify-start text-gray-700 font-semibold"> Base Controls </div>
            <input-file
                    label="Upload file"
                    v-model="upload_file"
                    :error="errors['upload_file']"
            >
            </input-file>
            <div class="mt-4 text-center">
                <button @click="submit()" class="inline-block px-4 py-2 rounded-lg shadow-md bg-teal-500 hover:bg-teal-400 focus:outline-none focus:shadow-outline text-white text-sm tracking-wider font-semibold">Submit</button>
            </div>
        </div>
    </div>
</template>

<script>
    import InputFile from "../Elements/Forms/Inputs/File";
    export default {
        name: "Forms",
        components: {
            InputFile,
        },
        data() {
            return {
                upload_file: '',
                errors: {},
            }
        },
        methods: {
            submit() {
                //Submit code...
            }
        }
    }
</script>

But I'm always getting an error:

Error in nextTick: "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."

I can see my event is getting emitted and upload_file has desired value set. To overcome this I made upload_file to object but this results in error and the component is also not shown. How can I fix this?

1 Answer 1

2

I believe the issue comes from trying to assign to the element's 'value' property (by binding it to prop.value)

When you're dealing with file-type elements, you can't write to the value property like you can with other types.

In your custom component's template, delete the binding, :value="value" and in its script either:

  • delete the prop value: Object or,
  • if you need to assign the value prop for v-model compatibility, assign it to File. eg: value: File

note: This will work, but you'll get a Vue warning: 'type check failed' for an invalid prop when the component is called without a supplied file.

ie...

<template>
    <div class="flex-col justify-start w-full">
        <div class="mt-2 block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">{{ label }}</div>
        <input
            class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
            :class="errorDisplay ? 'border-red-500 focus:bg-white focus:border-red-500': ''"
            type="file"
            @input="emitEvent($event)"
            ref="input_file"
        >
        <span v-if="hint" class="text-xs text-gray-400 font-medium">{{ hint }}</span>
        <span v-if="errorDisplay" class="text-xs text-pink-600 font-medium">{{ errorDisplay }}</span>
    </div>
</template>

<script>
    export default {
        name: "InputFile",
        props: {
            label: String,
            hint: {
                type: String,
                default: () => ''
            },
            error: {
                type: Array,
                default: () => []
            },
            placeholder: String,
            value: File,
        },
        methods: {
            emitEvent(event) {
                var reader = new FileReader();
                reader.readAsDataURL(event.target.files[0]);
                reader.onload = () => {
                    const docs = {
                        name: event.target.files[0].name,
                        size: event.target.files[0].size,
                        lastModifiedDate: event.target.files[0].lastModifiedDate,
                        base64: reader.result
                    };
                    console.log(docs);
                    this.$emit('input', docs)
                };
            }
        },
        computed: {
            errorDisplay() {
                if(this.error.length)
                    return this.error.join(', ');
                else
                    return '';
            }
        }
    }
</script>

should be ok.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.