0

I have two components TheCollapsible and TheBuyProduct. The first should be a structural component, making it possible to pass some children and a slot and render multiple collapsible itens (parents), that can expand and show its children. The second (which uses the first), should be a reactive screen to make it possible for the user to add items of some product. The problem here is the quantity of the product is not updated when the add or subtract functions are called.

Notice I have made two trials on reactivity: the first was to add a new property to the prop passed for my second component, this property is my quantity, but it doesn't work; the second trial was to create an array, where the index is the product id and the value is the quantity (which starts at zero). Both fail to be reactive.

I wander if this is because of the way my slot works. In my TheCollapsible component, I need a list of objects that has a property which is a new list (parent has property containing children). But as I said this is merely structural, each children HTML is passed as a slot and every prop I need in the slot I get through the v-slot.

TheCollapsible.vue

<template>
  <div class="ui-collapsible">
    <div
      class="ui-collapsible__parent"
      :key="parent_index"
      v-for="(parent, parent_index) in parents"
    >
      <div>
        <p>{{ parent.title }}</p>
        <button @click="openChildren(parent_index)">
          <img
            alt="Abrir"
            :id="`collapsible-icon-${parent_index}`"
            src="/img/icons/chevrons-up-down.svg"
          />
        </button>
      </div>
      <ul
        class="ui-collapsible__children"
        :id="`collapsible-child-${parent_index}`"
      >
        <li :key="index" v-for="(child, index) in parent[child_key]">
          <slot
            :child="child"
            :child_index="index"
            :parent_index="parent_index"
          />
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  props: ["child_key", "parents"],
  methods: {
    openChildren(index) {
      this.changeIconForOpenOrClosed(index);
      const open_child = document.getElementsByClassName(
        "ui-collapsible__children--active"
      )[0];
      if (open_child) {
        open_child.classList?.remove("ui-collapsible__children--active");
        open_child.classList.add("ui-collapsible__children");
      }
      const child = document.getElementById(`collapsible-child-${index}`);
      if (open_child?.id != child.id) {
        child.classList.remove("ui-collapsible__children");
        child.classList.add("ui-collapsible__children--active");
      }
    },
    changeIconForOpenOrClosed(index) {
      const opened_button = document.querySelector(
        '[src$="chevrons-down-up.svg"]'
      );
      if (opened_button?.src) {
        opened_button.src = "/img/icons/chevrons-up-down.svg";
      }

      const click_button = document.querySelector(`#collapsible-icon-${index}`);
      if (click_button?.id != opened_button?.id) {
        click_button.src = "/img/icons/chevrons-down-up.svg";
      }
    },
  },
};
</script>

TheBuyProduct.vue

<template>
  <div class="row">
    <div class="col-6">
      <img :alt="product.title" class="product__image" :src="product.image" />
      <h1>{{ product.title }}</h1>
      <div class="product__lead" v-html="product.lead"></div>
    </div>
    <div class="col-6">
      <h1>Acompanhamentos</h1>
      <the-collapsible
        child_key="items"
        :parents="product.accompaniment_categories"
        v-slot="{ child, child_index, parent_index }"
      >
        <div class="row product__accompaniment">
          <img :alt="child.title" class="col-2" :src="child.image" />
          <span class="col-4">{{ child.title }}</span>
          <div class="col-4 product__quantity">
            <button @click="subtract(child_index, parent_index, child.id)">
              <i class="fa fa-minus"></i>
            </button>
            <span>
              {{
                product.accompaniment_categories[parent_index].items[
                  child_index
                ].quantity
              }}
              -
              {{ accompaniments[child.id] }}
            </span>
            <button @click="add(child_index, parent_index, child.id)">
              <i class="fa fa-plus"></i>
            </button>
          </div>
          <span class="col-2">R${{ child.price }}</span>
        </div>
      </the-collapsible>
      <button class="ui-button product__button">Enviar pedido</button>
    </div>
  </div>
</template>

<script>
export default {
  props: ["product"],
  data() {
    return {
      accompaniments: [],
    };
  },
  created() {
    this.addPropToAccompaniments();
  },
  methods: {
    addPropToAccompaniments() {
      for (let i = 0; i < this.product.accompaniment_categories.length; i++) {
        const category_items = this.product.accompaniment_categories[i].items;
        for (let j = 0; j < category_items.length; j++) {
          category_items[j].quantity = 0;
          this.accompaniments[category_items[j].id] = 0;
        }
      }
    },
    add(i, j, id) {
      this.product.accompaniment_categories[i].items[j].quantity += 1;
      console.log("add:", this.accompaniments[id]);
      this.accompaniments[id] += 1;
    },
    subtract(i, j, id) {
      this.product.accompaniment_categories[i].items[j].quantity -= 1;
      console.log("sub:", this.accompaniments[id]);
      this.accompaniments[id] -= 1;
    },
  },
};
</script>

  • 1
    Vue does not react when we update an item directly at it's index. See this answer to understand the concept in detail- https://stackoverflow.com/a/74860530/11834856 – Neha Soni Feb 15 '23 at 04:25

0 Answers0