Loop through product items by category group in Vue template

Viewed 548

I want to render products by categories. So far I can render products with v-for, but I want similar products to render according to category.

I am fetching data from Django via DRF while using webpack and Vuex. I know I can't use v-if and v-for, so please tell me how can I render products while getting categories from filteredCategorys. I'm a new learner so sorry if I'm not clear about the question.

<base-card-food>
  <div v-if="isLoading">
    <base-spinner></base-spinner>
  </div>
  <ul v-else-if="hasFoods">
    <food-item
      v-for="food in filteredFoods"
      :key="food.id"
      :id="food.id"
      :name="food.name"
      :category="food.category"
      :description="food.description"
      :base_price="food.base_price"
      :photo="food.photo"
    ></food-item>
    <ul>
      <li v-for="cat in filteredCategorys" :key="cat.id">
        {{ cat.id }}
        {{ cat.name }}  
      </li>
    </ul>      
  </ul>
  <h3 v-else>No foods found.</h3>
</base-card-food>
computed: {
  filteredCategorys() {
    const categorys = this.$store.getters['categorys/categorys'];
    return categorys;
  },

  filteredFoods() {
    const foods = this.$store.getters['foods/foods'];
    return foods;
  }
}
1 Answers

Use a computed to rearrange the items array into an object whose keys are the category names. The corresponding value for each key will be an array of the items for that category. Here's what that looks like generally (there will be a specific demo for your data afterwards):

computed: {
  grouped() {
    const groups = {};
    this.items.forEach(item => {
      groups[item.category] = groups[item.category] || [];
      groups[item.category].push(item);
    })
    return groups;
  }
}

Use an outer loop for the categories, and an inner loop for the category items:

<div v-for="category in categories" :key="category.id">
  Category: {{ category.name }}
  <div v-for="item in grouped[category.name]" :key="item.id">
    Item: {{ item }}
  </div>
</div>

Here's a demo:

new Vue({
  el: "#app",
  data() {
    return {
      categories: [
        { id: 1, name: 'a' },
        { id: 2, name: 'b' },
        { id: 3, name: 'c' },
      ],
      items: [
        { id: 1, category: 'c', name: 'c1' },
        { id: 2, category: 'b', name: 'b1' },
        { id: 3, category: 'c', name: 'c2' },
        { id: 4, category: 'a', name: 'a1' },
        { id: 5, category: 'a', name: 'a2' },
        { id: 6, category: 'c', name: 'c3' },
        { id: 7, category: 'a', name: 'a3' },
        { id: 8, category: 'b', name: 'b2' },
      ]
    }
  },
  computed: {
    filteredFoods() {
      return this.items;  // mockup
    },
    filteredCategorys() {
      return this.categories;  // mockup
    },
    grouped() {
      const groups = {};
      this.filteredFoods.forEach(item => {
        groups[item.category] = groups[item.category] || [];
        groups[item.category].push(item);
      })
      return groups;
    }
  }
});
<div id="app">
  <div v-for="category in filteredCategorys" :key="category.id">
    Category: {{ category.name }}
    <div v-for="item in grouped[category.name]" :key="item.id">
      Item: {{ item }}
    </div>
  </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Since your data is coming from Vuex, you could create grouped as a getter instead of a computed. Then you would only need to import that getter.

Related