In my Vue app I have a product edit page where I can upload multiple attachments. For each attachment, the user must be able to enter a custom file name.
Therefore I have an array containing all the attachment items, where ach attachment item is rendered by a component called 'AttachmentsItem'.
The user can add extra attachments, or even remove them again.
The issue that I have is when removing items, for example:
Let's say I added 4 attachments in total. When I remove attachment number 3, the item is being removed correctly from the array. However, the 'file input html element' still seems to hold the file from attachment number 3 (which just has been removed).
What is the cause of this and how can I fix it?
The code of my parent component:
<template>
<div class="products-edit">
<!-- Spinner -->
<spinner v-show="isLoading"></spinner>
<!-- Form nl -->
<section v-if="!isLoading" class="form">
<form @submit.prevent="updateProduct">
<div class="form-group-wrapper">
<!-- Featured image -->
<div class="form-group">
<h2>Product afbeelding</h2>
<div class="img-placeholder">
<img v-if="!product_details" src="/assets/images/placeholder.png" alt="Featured image">
<img v-else-if="product_details.featured_image"
:src="'/images/' + product_details.featured_image" alt="Featured image">
<img v-else src="/assets/images/placeholder.png" alt="Featured image">
</div>
<input type="file" ref="featured_image" id="featured_image" name="featured_image"
v-on:change="handleFileUpload()">
<span v-if="errors && errors.featured_image" class="error-message">{{ errors.featured_image[0]
}}</span>
</div>
<!-- Attachments -->
<div class="form-group">
<h2>Bijlagen</h2>
<AttachmentsItem @removeAttachment="removeAttachment" @addAttachment="addAttachment" v-for="(attachment,index) in attachments" :key="index" :attachment="attachment" :index="index"></AttachmentsItem>
</div>
</div>
<!-- Submit button -->
<div class="form-group-wrapper">
<div class="form-group">
<button class="btn btn-primary btn-submit">
Opslaan
<spinner v-show="isPending"></spinner>
</button>
<p v-show="success" class="successMessage">
Wijzigigen opgeslagen
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</p>
</div>
</div>
</form>
</section>
</div>
</template>
<script>
import { SET_PAGE_TITLE, SET_PAGE_CTA, IS_LOADING } from '../../constants';
import spinner from '../../components/spinner.vue';
import AttachmentsItemVue from '../../components/AttachmentsItem.vue';
import AttachmentsItem from '../../components/AttachmentsItem.vue';
export default {
name: 'ProductsEdit',
components: {
spinner,
AttachmentsItemVue,
AttachmentsItem
},
mounted() {
this.setPageTitle;
this.loadData();
},
data() {
return {
product: null,
product_details: null,
featured_image: '',
attachments: [
{
file: '',
name: '',
}
],
isLoading: true,
isPending: false,
errors: null,
success: false,
}
},
methods: {
removeAttachment(index) {
if (this.attachments.length > 1) {
this.attachments.splice(index, 1);
} else {
this.attachments = [{}];
}
},
addAttachment() {
this.attachments.push({});
},
handleFileUpload() {
this.featured_image = this.$refs.featured_image.files[0];
},
loadData() {
axios.get(`/items/${this.$route.params.productId}/edit`)
.then((res) => {
this.product = res.data.product;
this.product_details = res.data.product_details;
this.isLoading = false;
})
.catch((err) => {
console.log(err);
this.isLoading = false;
});
},
updateProduct() {
this.isPending = true;
let f = new FormData();
f.append('featured_image', this.featured_image);
f.append('attachments', this.attachments);
axios.post(`/items/${this.$route.params.productId}/update`, f)
.then((res) => {
this.isPending = false;
this.success = true;
this.product_details = res.data.product_details;
setTimeout(() => {
this.success = false;
}, 3000);
})
.catch((err) => {
this.errors = err.response.data.errors;
this.isPending = false;
});
},
},
computed: {
setPageTitle() {
this.$store.dispatch('appStore/' + SET_PAGE_TITLE, 'Product aanpassen');
this.$store.dispatch('appStore/' + SET_PAGE_CTA, { 'title': 'Terug', 'url': '/products' });
},
currentLang() {
return this.$store.state.appStore.currentLanguage;
},
},
}
</script>
The code of my child component:
<template>
<div class="attachments-item">
<label :for="'attachment-' + index">Bijlage {{index + 1}}</label>
<div class="input-group">
<input type="file" :id="'attachment-' + index" ref="attachmentFile" v-on:change="handleFileUpload()" accept=".pdf,.docx">
<input type="text" placeholder="Bijlage naam" v-model="attachment.name">
<div class="action-buttons">
<svg @click="addAttachment" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg @click="removeAttachment" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AttachmentsItem',
props: {
attachment: {
type: Object,
required: true,
},
index: {
type: Number,
required: true,
},
},
methods: {
addAttachment() {
this.$emit('addAttachment');
},
removeAttachment() {
this.$emit('removeAttachment', this.index);
},
handleFileUpload() {
this.attachment.file = this.$refs.attachmentFile.files[0];
},
},
}
</script>