Skip to content

Vue Remove loop rendered component from DOM

I have image upload form for images to my website. When the user clicks on input images, he can choose multiple images. After selecting images, images are previewed and the user can select some meta info(Category, Type) about the image.

upload.vue

<template>
<div>
<div>
//Universal category select. this selection will apply to all comp.
<v-select  placeholder="Select Category"
          class="mt-2 md:w-1/2"
          :options="category"
          v-model="parentDesignCategory"
/>
<v-select
          placeholder="Select Type"
          class="mt-2 md:w-1/2"
          :options="type"
          v-model="parentDesignType"
        />
</div>
    <input
              type="file"
              accept="image/*"
              name="images"
              @change="uploadImage"
              id="images"
              multiple
            />

 <div class="flex flex-wrap">
        <div class="md:w-1/2" v-for="(file, index) in files" :key="index">
          <transition name="fade">
            <AdminFileUpload
              :file="file"
              :type="type"
              :category="category"
              :parentDesignType="parentDesignType"
              :parentDesignCategory="parentDesignCategory"
              @delete-row="deleteThisRow(index)"
            />
          </transition>
        </div>
      </div>
</div>
</template>
<script>
export default {
  name: "admin",
  // middleware: "auth",
  data: function() {
    return {
      files: [],
      parentDesignType: null,
      parentDesignCategory: null,
      type: ["1", "2", "3"],
      category: ["a","b","c"
      ]
    };
  },
  components: {},
  methods: {
    uploadImage(event) {
      let file = event.target.files;

      for (let i = 0; i < file.length; i++) {
        this.files.push(file[i]);
      }
    },
    deleteThisRow: function(index) {
      this.files.splice(index, 1);
    }
  }
};
</script>
<style scoped>
  .fade-enter-active {
    transition: opacity 1.5s;
  }

  .fade-leave-active {
    opacity: 0;
  }

  .fade-enter,
  .fade-leave-to {
    opacity: 0;
  }
</style>

And if all image falls in one category than a user can select one category from this page and all component follows this category.

fileUpload.vue Component

<template>
  <div>
    
      <div class="m-4">
        <form
          @submit.prevent="uploadImage"
          class="flex flex-wrap w-full shadow-lg border border-black"
          action="/upload"
        >
          <div class="w-full md:w-1/2 p-2">
            <div class="relative pb-1/1">
              <img :src="imageSrc" class="w-full absolute h-full" />
            </div>
          </div>

          <div class="flex flex-col w-full md:w-1/2 p-2">
            <v-select
              placeholder="Select Category"
              class="mt-2"
              :options="category"
              v-model="designCategory"
            ></v-select>
            <v-select
              placeholder="Select Type"
              class="mt-2"
              :options="type"
              v-model="designType"
            ></v-select>
            <input
              placeholder="likes"
              class="w-full  text-black border-2 mt-2 p-3 rounded-lg focus:outline-none focus:shadow-outline"
              type="number"
              v-model="designLikes"
            />

            <button
              @click="removeSelf"
              class="uppercase h-12 text-lg font-bold tracking-wide bg-primary text-gray-100 mt-2 p-3 rounded-lg w-full cursor-pointer"
              type="button"
            >
              Cancel
            </button>

            <button
              type="submit"
              class="uppercase mt-2 h-16 text-xl font-bold tracking-wide bg-accent text-gray-100 p-3 rounded-lg w-full transition duration-300 hover:opacity-80 cursor-pointer"
            >
              Upload
            </button>
          </div>
        </form>
      </div>
  </div>
</template>
<script>
import "vue-select/dist/vue-select.css";
export default {
  name: "fileUpload",
  middleware: "auth",
  props: [
    "file",
    "type",
    "category",
    "parentDesignCategory",
    "parentDesignType"
  ],
  data() {
    return {
   
      designCategory: this.parentDesignCategory,
      designType: this.parentDesignType,
      designLikes: null
    };
  },
  computed: {
    imageSrc: function() {
      return URL.createObjectURL(this.file);
    }
  },
  created() {},
  methods: {
    async uploadImage() {
      let formData = new FormData();
      const config = {
        headers: {
          "content-type": "multipart/form-data"
        }
      };

      formData.append("likes", this.designLikes);
      formData.append("image", this.file);
      formData.append("category", this.designCategory);
      formData.append("type", this.designType);
      await this.$axios
        .post("upload", formData, config)
        .then(response => {
          this.progress = 0;
          this.showToast("Photo Uploaded.", "success");
          // Delete coomponent when upload complete
          this.$emit("delete-row");
        })
        .catch(error => {
         
        });
    },
    removeSelf: function() {

          this.$emit("delete-row");

        
      });
    }
  }
};
</script>

Now my first and main problem is when the user removes the component from the dom, it removes the component but the Selected category/type stays in the same position. Suppose I chose 4 images. I set 2nd image category as “a”. When I remove 1st image. 1st image gets removed and 2nd image comes at 1st place but the category selected “a” remains on position 2.

Now 2nd problem is if I chose the category for the universal component in the parent page before selecting images it applies to all components. but after selecting images, Universal select doesn’t work.

3rd problem is transition doesn’t work on any component.

Answer

Simple answer is – you have to set an unique ID. Here is how you can solve that:

Changings in the template: First of all you need to set an id instead of using your index – setting an id makes it unique and that is what we need. So set your :key to file.id (we will create it in the script) and pass your file with deleteThisRow to your methods. Done!

<div class="md:w-1/2" v-for="file in files" :key="file.id">

//and change your index here to file here we will reference on the unique file we will create with the unique id we will set 
@delete-row="deleteThisRow(file)"

Changings in the script: Set your id = null in data() – that your created id will not be undefined. After that go to your methods and set your id = i – now it’s unique and could not be change anymore like your index could. Last thing you should do is to map over your files array and get the correct index which should be deleted with indexOf.

//in your data
data() {
  return {
    id: null,
  }
},

//in your methods
methods: {
    uploadImage(event) {
      let file = event.target.files;

      for (let i = 0; i < file.length; i++) {
        this.files.push({image:file[i], id : i}); //here you set your id to an unique number! (could be this.id you have to try)
      }
    },
    deleteThisRow: function(file) {
      var indexDelete = this.files.map(x => {
        return x.id;
      }).indexOf(file.id);
      
      this.files.splice(indexDelete, 1);
    }
  }

After all you have to pass your file.id to your child with following code:

<child  :uniqueID="file.id"
              :file="file.image">

and reference on this in your child with props

Hopefully I understood your question correct – than that should work out for your problem – please let me know if this works for you !

Additional Info: Please change all index-values to file.id – than everything is really unique.