Bootstrap Carousel containing SVG rerendering issue

I have an SVG component that I’m passing a ‘base’ of pre defined circles to and a list of modified circles for each carousel item. Ideally the ‘base’ circles would be rendered once and used for every carousel item and only the modified circles would be rerendered each time the user goes next/prev in the carousel. The issue is I can’t figure out a way to do it in vue that doesn’t result in the entire base being rerendered as well as the modified circles.

The following is the Carousel view where the data is passed to an SVG component

<template>
  <div class="container-fluid text-center" v-if="loading">
    <font-awesome-icon icon="cog" spin size="10x"></font-awesome-icon>
  </div>

  <div
    v-else
    id="carouselCircleLog"
    class="carousel"
    data-bs-ride="carousel"
    data-bs-interval="false"
    data-bs-wrap="false"
  >
    <div class="container-fluid">
      <div class="row justify-content-center">
        <div class="col-3 text-center">
          <div class="btn-group d-flex" role="group">
            <button
              v-on:click="start()"
              class="btn btn-outline-light rounded-0 shadow-none"
              :class="{ disabled: this.index === 0 }"
              type="button"
              data-bs-slide-to="0"
              data-bs-target="#carouselCircleLog"
            >
              <font-awesome-icon icon="angle-double-left"></font-awesome-icon>
            </button>
            <button
              v-on:click="decrementIndex()"
              class="btn btn-outline-light rounded-0 shadow-none"
              :class="{ disabled: this.index === 0 }"
              type="button"
              data-bs-target="#carouselCircleLog"
              data-bs-slide="prev"
            >
              <font-awesome-icon icon="angle-left"></font-awesome-icon>
            </button>
            <button
              v-on:click="incrementIndex()"
              class="btn btn-outline-light rounded-0 shadow-none"
              :class="{ disabled: this.index === lastIndex }"
              type="button"
              data-bs-target="#carouselCircleLog"
              data-bs-slide="next"
            >
              <font-awesome-icon icon="angle-right"></font-awesome-icon>
            </button>
            <button
              v-on:click="last()"
              class="btn btn-outline-light rounded-0 shadow-none"
              :class="{ disabled: this.index === lastIndex }"
              type="button"
              :data-bs-slide-to="lastIndex"
              data-bs-target="#carouselCircleLog"
            >
              <font-awesome-icon icon="angle-double-right"></font-awesome-icon>
            </button>
          </div>
        </div>
      </div>
    </div>
    <div class="carousel-inner">
      <div
        class="carousel-item"
        v-for="(item, index) in circles.modifiedCircles"
        :key="index"
        :class="{ active: index === 0 }"
      >
        <div class="row">
          <div class="col-5">
            <SvgComponent
              :base="circles.baseCircles"
              :modified="item"
            ></SvgComponent>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import SvgComponent from "@/components/SvgComponent.vue";

export default {
  name: "CircleLog",
  components: { SvgComponent },
  data() {
    return {
      // Example data
      circles: {
        baseCircles: {
          5: {
            cx: "5000",
            cy: "5000",
            r: "46",
            fill: "#ffffff"
          },
          6: {
            cx: "5100",
            cy: "5000",
            r: "46",
            fill: "#ffffff"
          },
          7: {
            cx: "5200",
            cy: "5000",
            r: "46",
            fill: "#ffffff"
          },
          8: {
            cx: "5300",
            cy: "5000",
            r: "46",
            fill: "#ffffff"
          }
        },
        modifiedCircles: [
          {
            5: {
              cx: "5000",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            6: {
              cx: "5100",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            7: {
              cx: "5200",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            }
          },
          {
            5: {
              cx: "5000",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            6: {
              cx: "5100",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            7: {
              cx: "5200",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            8: {
              cx: "5300",
              cy: "5000",
              r: "46",
              fill: "#13ff00"
            }
          },
          {
            5: {
              cx: "5000",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            6: {
              cx: "5100",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            7: {
              cx: "5200",
              cy: "5000",
              r: "46",
              fill: "#fff111"
            },
            8: {
              cx: "5300",
              cy: "5000",
              r: "46",
              fill: "#ff0000"
            }
          }
        ]
      },
      index: 0,
      loading: false
    };
  },
  // Used for carousel controls
  computed: {
    lastIndex() {
      return this.circles.modifiedCircles.length - 1;
    }
  },
  created() {},
  // More carousel controls
  methods: {
    start() {
      this.index = 0;
    },
    last() {
      this.index = this.circles.modifiedCircles.length - 1;
    },
    incrementIndex() {
      this.index = this.index + 1;
    },
    decrementIndex() {
      this.index = this.index - 1;
    }
  }
};
</script>

This is the SVG component

<template>
  <svg
    viewBox="-7334.32 -7334.32 14841.83 14841.83"
    style="background-color:transparent;"
    id="viewBox"
  >
    <template v-for="(value, key, index) in base">
      <circle
        v-if="key in modified"
        :key="index"
        :cx="modified[key].cx"
        :cy="modified[key].cy"
        :r="modified[key].r"
        :fill="modified[key].fill"
      ></circle>
      <circle
        v-else
        :key="index"
        :cx="value.cx"
        :cy="value.cy"
        :r="value.r"
        :fill="value.fill"
      ></circle>
    </template>
  </svg>
</template>

<script>
export default {
  name: "SvgComponent",
  props: {
    base: Object,
    modified: Object
  }
};
</script>

Any time a component prop changes, the component will be re-rendered. For your use case, you need to seperate the SVG component into two components.

One that acts as your base that will only render once because the props you pass it don’t change. The other that is your dynamic component that will re-render each time you change slides.

As a side note, using v-if within v-for is poor for performance. Instead, use a computed - but for the solution I described above, you shouldn’t need a v-if anymore.

Thanks for the help I’ll break them up into separate components.

Follow up question now is once they are broken into separate components will I be able to modify the base circles from the modified circles component? If not I can simply draw the modified ones on top.

You can emit an event from your modified circles component which gets handled by your parent component to alter the base circle data.

Ok cool I didn’t know that was a thing. So if I emit the event and modify the base circle data won’t the entire base circle component rerender since I modified the props?

Yes, if you change the prop data that the component uses then it will re-render.