title: VueJS: Why parent components method unable to delete/destroy child's child (`vue2-dropzone`) component entirely?
body: <p>I am creating a slider in vuejs and am using <a href="https://rowanwins.github.io/vue-dropzone/docs/dist/#/installation" rel="nofollow noreferrer">vue2-dropzone plugin</a> for file uploads where each slide (<code>slide-template.vue</code>) has a <code>vue2-dropzone</code> component.</p>
  <p>When app loads, image files are <em>manually added</em> in each <code>vue2-dropzone</code> (<code>manuallyAddFile</code> plugins API) queried from image API (hosted on heroku)</p>
  <p>The issue is when I delete the first slide, calling the parent's (slider.vue) method <code>removeSlideFn</code> (passed down to child as prop) from child (<code>slide-template.vue</code>) component first slide is deleted but not entirely the dropzone images of the first slides are not destroyed and remains in the DOM, instead images of <code>slide2</code>, (the next slide) are deleted from the DOM (Pls try it once on codesandbox demo to actually know what I am mean). This does not happen when I delete <code>slide2</code> or <code>slide3</code> but only on <code>slide1</code>.</p>
  <p><a href="https://codesandbox.io/s/vuedropzone-slide-delete-issue-xim96-3g677" rel="nofollow noreferrer">CodeSandBox Demo</a></p>
  <h2>App.vue</h2>
  <pre class="lang-html prettyprint-override"><code>&lt;template&gt;
    &lt;div id=&quot;app&quot;&gt;
      &lt;img width=&quot;15%&quot; src=&quot;./assets/logo.png&quot;&gt;
      &lt;slider /&gt;
    &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import slider from &quot;./components/slider&quot;;
  
  export default {
    name: &quot;App&quot;,
    components: {
      slider
    }
  };
  &lt;/script&gt;
  </code></pre>
  <h3>components\slider.vue (parent)</h3>
  <pre class="lang-html prettyprint-override"><code>&lt;template&gt;
      &lt;div&gt;
          &lt;hooper ref=&quot;carousel&quot; :style=&quot;hooperStyle&quot; :settings=&quot;hooperSettings&quot;&gt;
              &lt;slide :key=&quot;idx&quot; :index=&quot;idx&quot; v-for=&quot;(slideItem, idx) in slideList&quot;&gt;
                  &lt;slide-template
                      :slideItem=&quot;slideItem&quot; 
                      :slideIDX=&quot;idx&quot;
                      :removeSlideFn=&quot;removeCurrSlide&quot; /&gt;
              &lt;/slide&gt;
  
              &lt;hooper-navigation slot=&quot;hooper-addons&quot;&gt;&lt;/hooper-navigation&gt;
              &lt;hooper-pagination slot=&quot;hooper-addons&quot;&gt;&lt;/hooper-pagination&gt;
          &lt;/hooper&gt;
  
              &lt;div class=&quot;buttons has-addons is-centered is-inline-block&quot;&gt;
                  &lt;button class=&quot;button is-info&quot; @click=&quot;slidePrev&quot;&gt;PREV&lt;/button&gt;
                  &lt;button class=&quot;button is-info&quot; @click=&quot;slideNext&quot;&gt;NEXT&lt;/button&gt;
              &lt;/div&gt;
      &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import {
    Hooper,
    Slide,
    Pagination as HooperPagination,
    Navigation as HooperNavigation
  } from &quot;hooper&quot;;
  import &quot;hooper/dist/hooper.css&quot;;
  
  import slideTemplate from &quot;./slide-template.vue&quot;;
  import { slideShowsRef } from &quot;./utils.js&quot;;
  
  export default {
    data() {
      return {
        sliderRef: &quot;SlideShow 1&quot;,
        slideList: [],
        hooperSettings: {
          autoPlay: false,
          centerMode: true,
          progress: true
        },
        hooperStyle: {
          height: &quot;265px&quot;
        }
      };
    },
    methods: {
      slidePrev() {
        this.$refs.carousel.slidePrev();
      },
      slideNext() {
        this.$refs.carousel.slideNext();
      },
  
      //Removes slider identified by IDX
      removeCurrSlide(idx) {
        this.slideList.splice(idx, 1);
      },
  
      // Fetch data from firebase
      getSliderData() {
        let that = this;
        let mySliderRef = slideShowsRef.child(this.sliderRef);
        mySliderRef.once(&quot;value&quot;, snap =&gt; {
          if (snap.val()) {
            this.slideList = [];
            snap.forEach(childSnapshot =&gt; {
              that.slideList.push(childSnapshot.val());
            });
          }
        });
      }
    },
    watch: {
      getSlider: {
        handler: &quot;getSliderData&quot;,
        immediate: true
      }
    },
    components: {
      slideTemplate,
      Hooper,
      Slide,
      HooperPagination,
      HooperNavigation
    }
  };
  &lt;/script&gt;
  </code></pre>
  <h3>components/slide-template.vue (child, with vue2-dropzone)</h3>
  <pre class="lang-html prettyprint-override"><code>&lt;template&gt;
      &lt;div class=&quot;slide-wrapper&quot;&gt;
          &lt;slideTitle :heading=&quot;slideItem.heading&quot; /&gt;
          &lt;a class=&quot;button delete remove-curr-slide&quot; @click=&quot;deleteCurrSlide(slideIDX)&quot; &gt;&lt;/a&gt;
      
              &lt;vue2Dropzone
                  @vdropzone-file-added=&quot;fileWasAdded&quot;
                  @vdropzone-thumbnail=&quot;thumbnail&quot;
                  @vdropzone-mounted=&quot;manuallyAddFiles(slideItem.zones)&quot;
                  :destroyDropzone=&quot;false&quot;
                  :include-styling=&quot;false&quot;
                  :ref=&quot;`dropZone${ slideIDX }`&quot;
                  :id=&quot;`customDropZone${ slideIDX }`&quot;
                  :options=&quot;dropzoneOptions&quot;&gt;
              &lt;/vue2Dropzone&gt;
      &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import slideTitle from &quot;./slide-title.vue&quot;;
  import vue2Dropzone from &quot;@dkjain/vue2-dropzone&quot;;
  import { generate_ObjURLfromImageStream, asyncForEach } from &quot;./utils.js&quot;;
  
  export default {
    props: [&quot;slideIDX&quot;, &quot;slideItem&quot;, &quot;removeSlideFn&quot;],
    data() {
      return {
        dropzoneOptions: {
          url: &quot;https://vuejs-slider-node-lokijs-api.herokuapp.com/imageUpload&quot;,
          thumbnailWidth: 150,
          autoProcessQueue: false,
          maxFiles: 1,
          maxFilesize: 2,
          addRemoveLinks: true,
          previewTemplate: this.template()
        }
      };
    },
    components: {
      slideTitle,
      vue2Dropzone
    },
    methods: {
      template: function() {
        return `&lt;div class=&quot;dz-preview dz-file-preview&quot;&gt;
                          &lt;div class=&quot;dz-image&quot;&gt;
                              &lt;img data-dz-thumbnail/&gt;
                          &lt;/div&gt;
                          &lt;div class=&quot;dz-details&quot;&gt;
                              &lt;!-- &lt;div class=&quot;dz-size&quot;&gt;&lt;span data-dz-size&gt;&lt;/span&gt;&lt;/div&gt; --&gt;
                              
                              &lt;!-- &lt;div class=&quot;dz-filename&quot;&gt;&lt;span data-dz-name&gt;&lt;/span&gt;&lt;/div&gt;  --&gt;
                          &lt;/div&gt;
                          &lt;div class=&quot;dz-progress&quot;&gt;&lt;span class=&quot;dz-upload&quot; data-dz-uploadprogress&gt;&lt;/span&gt;&lt;/div&gt;
                          &lt;div class=&quot;dz-error-message&quot;&gt;&lt;span data-dz-errormessage&gt;&lt;/span&gt;&lt;/div&gt;
                          &lt;div class=&quot;dz-success-mark&quot;&gt;&lt;i class=&quot;fa fa-check&quot;&gt;&lt;/i&gt;&lt;/div&gt;
                          &lt;div class=&quot;dz-error-mark&quot;&gt;&lt;i class=&quot;fa fa-close&quot;&gt;&lt;/i&gt;&lt;/div&gt;
                      &lt;/div&gt;`;
      },
      thumbnail: function(file, dataUrl) {
        var j, len, ref, thumbnailElement;
        if (file.previewElement) {
          file.previewElement.classList.remove(&quot;dz-file-preview&quot;);
          ref = file.previewElement.querySelectorAll(&quot;[data-dz-thumbnail]&quot;);
          for (j = 0, len = ref.length; j &lt; len; j++) {
            thumbnailElement = ref[j];
            thumbnailElement.alt = file.name;
          }
          thumbnailElement.src = dataUrl;
          return setTimeout(
            (function(_this) {
              return function() {
                return file.previewElement.classList.add(&quot;dz-image-preview&quot;);
              };
            })(this),
            1
          );
        }
      },
      // Drag &amp; Drop Events
      async manuallyAddFiles(zoneData) {
        if (zoneData) {
          let dropZone = `dropZone${this.slideIDX}`;
  
          asyncForEach(zoneData, async fileInfo =&gt; {
            var mockFile = {
              size: fileInfo.size,
              name: fileInfo.originalName || fileInfo.name,
              type: fileInfo.type,
              id: fileInfo.id,
              childZoneId: fileInfo.childZoneId
            };
            let url = `https://vuejs-slider-node-lokijs-api.herokuapp.com/images/${
              fileInfo.id
            }`;
  
            let objURL = await generate_ObjURLfromImageStream(url);
            this.$refs[dropZone].manuallyAddFile(mockFile, objURL);
          });
        }
      },
  
      fileWasAdded(file) {
        console.log(&quot;Successfully Loaded Files from Server&quot;);
      },
  
      deleteCurrSlide(idx) {
        this.removeSlideFn(idx);
      }
    }
  };
  &lt;/script&gt;
  
  &lt;style lang=&quot;scss&quot;&gt;
  .slide-wrapper {
    position: relative;
  }
  [id^=&quot;customDropZone&quot;] {
    background-color: orange;
    font-family: &quot;Arial&quot;, sans-serif;
    letter-spacing: 0.2px;
    /* color: #777; */
    transition: background-color 0.2s linear;
    //   height: 200px;
    padding: 40px;
  }
  
  [id^=&quot;customDropZone&quot;] .dz-preview {
    width: 160px;
    display: inline-block;
  }
  [id^=&quot;customDropZone&quot;] .dz-preview .dz-image {
    width: 80px;
    height: 80px;
    margin-left: 40px;
    margin-bottom: 10px;
  }
  [id^=&quot;customDropZone&quot;] .dz-preview .dz-image &gt; div {
    width: inherit;
    height: inherit;
    //   border-radius: 50%;
    background-size: contain;
  }
  [id^=&quot;customDropZone&quot;] .dz-preview .dz-image &gt; img {
    width: 100%;
  }
  
  [id^=&quot;customDropZone&quot;] .dz-preview .dz-details {
    color: white;
    transition: opacity 0.2s linear;
    text-align: center;
  }
  [id^=&quot;customDropZone&quot;] .dz-success-mark,
  .dz-error-mark {
    display: none;
  }
  .dz-size {
    border: 2px solid blue;
  }
  
  #previews {
    border: 2px solid red;
    min-height: 50px;
    z-index: 9999;
  }
  
  .button.delete.remove-curr-slide {
    padding: 12px;
    margin-top: 5px;
    margin-left: 5px;
    position: absolute;
    right: 150px;
    background-color: red;
  }
  &lt;/style&gt;
  </code></pre>
  <h3>slide-title.vue (not that important)</h3>
  <pre class="lang-html prettyprint-override"><code>&lt;template&gt;
      &lt;h2 contenteditable @blur=&quot;save&quot;&gt; {{ heading }} &lt;/h2&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  export default {
    props: [&quot;heading&quot;],
    methods: {
      save() {
        this.$emit(&quot;onTitleUpdate&quot;, event.target.innerText.trim());
      }
    }
  };
  &lt;/script&gt;
  </code></pre>
  <h3>utils.js (utility)</h3>
  <pre class="lang-js prettyprint-override"><code>export async function generate_ObjURLfromImageStream(url) {
    return await fetch(url)
      .then(response =&gt; {
        return response.body;
      })
      .then(rs =&gt; {
        const reader = rs.getReader();
  
        return new ReadableStream({
          async start(controller) {
            while (true) {
              const { done, value } = await reader.read();
  
              // When no more data needs to be consumed, break the reading
              if (done) {
                break;
              }
  
              // Enqueue the next data chunk into our target stream
              controller.enqueue(value);
            }
  
            // Close the stream
            controller.close();
            reader.releaseLock();
          }
        });
      })
      // Create a new response out of the stream
      .then(rs =&gt; new Response(rs))
      // Create an object URL for the response
      .then(response =&gt; {
        return response.blob();
      })
      .then(blob =&gt; {
        // generate a objectURL (blob:url/&lt;uuid&gt; list)
        return URL.createObjectURL(blob);
      })
      .catch(console.error);
  }
  </code></pre>
  <p>Technically this is how the app works, slider.vue loads &amp; fetches data from database (firebase) and stores in a data array <code>slideList</code>, loops over the <code>slideList</code> &amp; passes each slideData (prop <code>slideItem</code>) to vue-dropzone component (in slide-template.vue), when dropzone mounts it fires the <code>manuallyAddFiles(slideItem.zones)</code> on the <code>@vdropzone-mounted</code> custom event.</p>
  <p>The async <code>manuallyAddFiles()</code> fetches image from an API (hosted on heroku), creates (generate_ObjURLfromImageStream(url)) a unique blob URL for the image (blob:/) and then calls plugins API <code>dropZone.manuallyAddFile()</code> to load the image into the corresponding dropzone.</p>
  <p>To delete the current slide, child's <code>deleteCurrSlide()</code> calls parent's (slider.vue) <code>removeSlideFn</code> (passed as prop) method with the <code>idx</code> of current slide. The <code>removeSlideFn</code> use <code>splice</code> to remove the item at the corresponding array idx <code>this.slideList.splice(idx, 1)</code>.</p>
  <p>The problem is when I delete the first slide, first slide is deleted but not entirely, the dropzone images of the first slides are not destroyed and still remains in the DOM, instead the images of <code>slide2</code>, (the next slide) are deleted from the DOM.</p>
  <p><a href="https://codesandbox.io/s/vuedropzone-slide-delete-issue-xim96-3g677" rel="nofollow noreferrer">CodeSandBox Demo</a></p>
  <p>I am not sure what is causing the issue, may it's due to something in the vue's reactivity system OR <a href="https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats" rel="nofollow noreferrer">Vue's Array reactivity caveat</a> that is causing this.</p>
  <p>Can anybody pls help me understand &amp; resolve this and if possible point out the reason to the root of the problem.</p>
  <p>Your help is much appreciated.</p>
  <p>Thanks,</p>