Dynamic data within v-for loops

Hello! This is my first post on this forum, so I ask you point to instructions if I have missed something general. I think I read all required rules.

Before reading on, I will not be posting a codePen as my question seems more Vue internals related. That being said, I will provide some examples and details.

I am currently working with a carousel library called vue-glide-js. You pass the library some details in the form of an array (or you can hard code if you would like. buy why?), and it generates a slide for each of your items.

My current example:

<vue-glide
      ref="carousel"
      v-model="active"
      @glide:run-before="handleBeforeTranslation"
      @glide:run-after="handleTranslated"
      @glide:move="handleDrag"
      @glide:translate-jump="handleJump"
      @glide:slide-click="handleSlideClick"
      :options="options"
    >
      <vue-glide-slide v-for="(card, index) in cards" :key="index">
        <PassiveCard
          :index="index"
          :title="card.title"
          :subTitle="card.subtitle"
          :image="card.image"
          :button="card.buttonTitle"
          :showPaddles="false"
          :target="card.target"
          :contentPath="card.contentPath"
          :sponsorships="card.sponsorships"
          :analyticsID="card.analyticsID"
          :type="card.cardType"
        />
      </vue-glide-slide>
    </vue-glide>

data() [ some of it, since it is large ]:

      cards: [],
      oldCards: [],
      newCardsOrdered: null,
      carouselBuffer: 5,
      active: null,
      centerCard: null,
      stopAutoPlay: false,
      options: {
        type: "carousel",
        perView: 1,
        animationDuration: 1000,
        autoplay: 20000,
        startAt: 0,
        focusAt: 0,
        gap: 100,
        peek: { before: 150, after: 150 },
        hoverpause: false,
        keyboard: false,
        bound: false
      }

As you can see, it has the main carousel component vue-glide and I generate a vue-glide-slide for each of the cards in my array. I am currently using index and the bind:key as I have tried taking the data and adding a uuid() to the data and applying the uuid to the key and still have the same problem.

The fundamental problem I am having is that I have to update the this.cards array on every translate (handleTranslated method). I am using this function:

handleBeforeTranslation(move) {
      this.removeLeaderboardAds();
      this.updateAdLabel(false);
      const { direction } = move;
      if (direction !== "=") {
        const card = this.getCardInDirection(move.direction);
        this.updateCards(direction, card);
      }
    },

The updateCards method:

updateCards(direction, card) {
      let replacementIndex = -1;
      if (direction === ">") {
        replacementIndex = this.centerCardIndex + 2;
      } else replacementIndex = this.centerCardIndex - 2;
      replacementIndex = (replacementIndex + 5) % this.carouselBuffer;
      console.log("replacementIndex:", replacementIndex);
      console.log("centerIndex", this.centerCardIndex);

      this.$set(this.cards, replacementIndex, card);
    },

You can see that I am currently using the Vue.$set method to notify vue that I am making a change to the array. However, while the array IS updating the DOM is not updating and the carousel “Jumps” when it has translated over all [5] items. (there will always and forever be a total of 5 items in the array. Moreover, there will always be an item being removed and added based on the direction the carousel moves.)

Things I have tried:

  1. using the javascript slice method.
  2. computed property just returning this.cards post update.
  3. keeping track of the old cards and returning those after 5 in hopes the DOM doesn’t rerender the 3 cards in the view.
  4. Using the Vue provided Vue.$set method.
  5. hard coding all 5 cards hoping Vue will just update the cards updated.

I am hoping that this community has dealt with having to dynamically update arrays that are loaded in a v-for. I understand that on mount() these items are on the DOM, but how I can request Vue to make the required updates without rerendering the all elements and just the requested change? If you are thinking that it is in fact a :key problem, please show me how I can better track if the data itself has to manipulated prior to being provided in the loop. I will admit I am massively missing a fundamental problem here as simple todo apps update v-for loops all the time and the DOM does update and add. Could it be an internal library to vue-glide-js since they are using slots to hold the cards?

Your genius is greatly appreciated and happy 2020!
-Xen

What does passivecard look like?

<template>
  <div :style="backgroundStyle" class="carousel-card-container">
    <div v-if="type === 'cyo'" class="carousel-card-overlay">
      <h2 class="cyo-title">{{ title }}</h2>
      <h3 v-if="subTitle" class="cyo-subTitle">{{ subTitle }}</h3>
      <transition name="fade-overlay">
        <div v-if="show" @click="handleOverlayClick" class="carousel-overlay">
          <h2 class="carousel-cta">Tap Here to begin</h2>
          <div class="carousel-cta-image-container">
            <img class="carousel-cta-image" :src="carouselOverlayImg" />
          </div>
        </div>
      </transition>
    </div>
    <div v-else-if="type === 'card'" class="carousel-card-overlay">
      <h2 class="carousel-title">{{ title }}</h2>
      <h3 v-if="subTitle" class="subTitle">{{ subTitle }}</h3>
      <button :style="buttonStyle" class="btn">{{ button }}</button>
      <transition name="fade">
        <div
          v-if="showPaddles"
          :style="rightPaddle"
          class="paddle right-paddle"
        ></div>
      </transition>
      <transition name="fade">
        <div
          v-if="showPaddles"
          :style="leftPaddle"
          class="paddle left-paddle"
        ></div>
      </transition>

      <transition name="fade-overlay">
        <div v-if="show" @click="handleOverlayClick" class="carousel-overlay">
          <h2 class="carousel-cta">Tap Here to begin</h2>
          <div class="carousel-cta-image-container">
            <img class="carousel-cta-image" :src="carouselOverlayImg" />
          </div>
        </div>
      </transition>
    </div>
    <div v-else class="carousel-card-overlay">
      <iframe
        :src="contentPath"
        width="780px"
        height="1170px"
        frameborder="0"
        class="passive-ad"
      ></iframe>
      <div
        class="carousel-iframe-overlay"
        @click="handleOverlayClick"
        v-touch:swipe="handleOverlaySwipe"
      ></div>
    </div>
  </div>
</template>

Script:

import config from "../utils/config";
import { mapActions, mapGetters, mapState } from "vuex";

export default {
  name: "passiveCard",
  data() {
    return {
      showPaddles: false,
      show: false,
      carouselOverlayImg: config.carouselOverlayImg
    };
  },
  props: {
    index: {
      type: Number,
      required: true
    },
    title: {
      type: String,
      required: true
    },
    subTitle: {
      type: String,
      required: false
    },
    image: {
      type: String,
      required: true
    },
    button: {
      type: String,
      required: true
    },
    target: {
      type: [Number, String],
      required: false
    },
    contentPath: {
      type: String,
      required: false
    },
    sponsorships: {
      type: Array,
      required: false
    },
    analyticsID: {
      type: [String, Number],
      required: false
    },
    type: {
      type: String,
      required: true
    }
  },
  watch: {
    showCTA(show) {
      if (show) {
        if (this.index === this.centerCardIndex) {
          this.show = true;
        }
      } else {
        this.show = false;
      }
    },
    centerCardIndex(newVal) {
      if (this.index === newVal) {
        this.showPaddles = true;
      } else {
        this.showPaddles = false;
      }
      if (this.index === newVal && this.type === "ad") {
        this.updateAdLabel(true);
      }
    }
  },
  computed: {
    ...mapState("appState", ["centerCardIndex"]),
    ...mapGetters({
      colors: "appData/getCurrentColors",
      showCTA: "appState/showCarouselCTA"
    }),
    buttonStyle() {
      return {
        backgroundColor: this.colors.ctaColor
      };
    },
    backgroundStyle() {
      return {
        backgroundImage: `url("${this.image}")`
      };
    },
    leftPaddle() {
      return {
        backgroundImage:
          "url(" + require("@/assets/test/navigation/paddle-left.png") + ")"
      };
    },
    rightPaddle() {
      return {
        backgroundImage:
          "url(" + require("@/assets/test/navigation/paddle-right.png") + ")"
      };
    }
  },
  methods: {
    ...mapActions({
      updateAdLabel: "appState/UPDATE_SHOW_PASSIVE_AD"
    }),
    handleOverlayClick(event) {
      console.log(event);
    },
    handleOverlaySwipe(direction) {
      console.log(direction);
    }
  }
};

wow! I would try to replace it with just a div with card.title to isolate the problem. And yes, I think it is better to use something like card.id as key.

Yea, It’s a massive project. I have everything working accept this. one… issue that might make me start all over. :frowning. I will try to place just a div and update the key again to see if I am missing something.

here is a baseline codeSandbox if someone has an answer.