user10745267
user10745267

Reputation: 81

Cannot get computed property (array)

Trying to get a 'displayImages' array as a computed property. Using a default 'selected' property = 0. this.selected changes accordingly on mouseover and click events.

When trying to get the computed 'displayImages' it says:

"this.variations[this.selected] is undefined."

I'm using an api to get my product data and images.

<template>
  <div id="product-page">
    <v-card width="100%" class="product-card">
      <div class="image-carousel">
        <v-carousel height="100%" continuos hide-delimiters>
          <v-carousel-item
            v-for="(image, i) in displayImages"
            :key="i"
            :src="image"
          >
          </v-carousel-item>
        </v-carousel>
      </div>
      <div class="details">
        <h2>{{ this.title }}<br />Price: ${{ this.price }}</h2>
        <p>{{ this.details }}</p>
        <ul style="list-style: none; padding: 0">
          <li
            style="border: 1px solid red; width: auto"
            v-for="(color, index) in variations"
            :key="index"
            @mouseover="updateProduct(index)"
            @click="updateProduct(index)"
          >
            {{ color.color }}
          </li>
        </ul>
        <div class="buttons">
          <v-btn outlined rounded
            >ADD TO CART<v-icon right>mdi-cart-plus</v-icon></v-btn
          >
          <router-link to="/shop">
            <v-btn text outlined rounded> BACK TO SHOP</v-btn>
          </router-link>
        </div>
      </div>
    </v-card>
  </div>
</template>

<script>
export default {
  name: "Product",
  props: ["APIurl"],
  data: () => ({
    title: "",
    details: "",
    price: "",
    variations: [],
    selected: 0,
  }),
  created() {
    fetch(this.APIurl + "/products/" + this.$route.params.id)
      .then((response) => response.json())
      .then((data) => {
        //console.log(data);
        this.title = data.title;
        this.details = data.details.toLowerCase();
        this.price = data.price;
        data.variations.forEach((element) => {
          let imagesArray = element.photos.map(
            (image) => this.APIurl + image.url
          );
          this.variations.push({
            color: element.title,
            images: imagesArray,
            qty: element.qty,
            productId: element.productId,
          });
        });
      });
  },
  computed: {
    displayImages() {
      return this.variations[this.selected].images;
    },
  },
  methods: {
    updateProduct: function (index) {
      this.selected = index;
      console.log(index);
    }
  },
};
</script>

Upvotes: 0

Views: 355

Answers (2)

Terry
Terry

Reputation: 66228

To properly expand on my comment, the reason why you are running into an error is because when the computed is being accessed in the template, this.variations is an empty array. It is only being populated asynchronously, so chances are, it is empty when VueJS attempts to use it when rendering the virtual DOM.

For that reason, accessing an item within it by index (given as this.selected) will return undefined. Therefore, attempting to access a property called images in the undefined object will return an error.

To fix this problem, all you need is to introduce a guard clause in your computed as such:

computed: {
  displayImages() {
    const variation = this.variations[this.selected];

    // GUARD: If variation is falsy, return empty array
    if (!variation) {
      return [];
    }

    return variation.images;
  },
}

Bonus tip: if you one day would consider using TypeScript, you can even simplify it as such... but that's a discussion for another day ;) for now, optional chaining and the nullish coalescing operator is only supported by bleeding edge versions of evergreen browsers.

computed: {
  displayImages() {
    return this.variations[this.selected]?.images ?? [];
  },
}

Upvotes: 1

Gabriel Willemann
Gabriel Willemann

Reputation: 1921

For avoid this kind of error, you must to use the safe navigation property.

Remember, it's useful just when the app is loading.

Try something like that:

<script>
export default {
  name: 'Product',
  computed: {
    displayImages() {
      if (this.variations[this.selected]) {
        return this.variations[this.selected].images;
      }
      return [];
    },
  },
};
</script>

Upvotes: 1

Related Questions