Skip to content
Advertisement

Why the sort method not working in the parent component?

This is the first project for me in VueJS. I have a product list and want to sort it by price. I built two components and tried to pass a sort method to the parent from the child component (dropdown button) by emitting an event. but after a lot of attempts, I can’t find the wrong with my code, any help!

This Child Component:

<template>
  <div class="dropdown">
    <button
      @click="toggleShow(); $emit('sortPrice')"
      class="dropbtn"
    >
      {{ title }}
      <span class="material-icons-outlined"> {{ icon }} </span>
    </button>

    <div v-if="showMenu" class="menu">
      <div class="menu-item" v-for="(item, index) in this.items" :key="index">
        {{ item }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Dropdown-menu",

  props: {
    title: String,
    icon: String,
    items: {
      type: Object,
      required: true,
    },
  },

  data() {
    return {
      showMenu: false
    };
  },

  methods: {
    toggleShow: function () {
      this.showMenu = !this.showMenu;
    },

    sortPrice: function () {
      this.$emit("sort", this.sortPrice);
    },
  },
};
</script>

This Parent Component:

<template>
    <dropdown
          :title="sortedBy"
          :items="arrangements"
          :icon="material_icons"
          @sort="sortByPrice"
    ></dropdown>
</template>

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

export default {
  components: {
    Dropdown,
  },

  data() {
    return {
      sortedBy: "Featured",
      arrangements: ["Featured", "Lowest", "Highest"],
      material_icons: "expand_more",
      productData: require("@/data/store-data.json"),
    };
  },
  methods: {
    sortByPrice: function () {
      let realProducts = this.productData.products;
      let sortedProducts = realProducts.sort((a, b) => {
        if (this.sortedBy === "Highest") {
          return b.price - a.price;
        } else if (this.sortedBy === "Lowest") {
          return a.price - b.price;
        }
      });
      return sortedProducts;
    },
  },
};
</script>

Advertisement

Answer

Suggestions:

  • emit when an individual item is clicked, not when the button is clicked. You want to emit when the user makes a selection
  • So this means calling the sortPrice function from the menu-item div via @click="sortPrice(item)"
  • Then in the sortPrice function, pass in the item paramter, function (item) { and pass it as a second parameter to your emit call: this.$emit("sort", item);. The parent must know what was selected
  • In the parent component, sortByPrice function, accept the item parameter, sortByPrice: function (item) { and use it to set the sortedBy property: this.sortedBy = item;
  • Do the sorting in a computed property that is then displayed, here in my example called sortedProducts.

For example, the parent:

<template>
  <h2>Main App</h2>
  <dropdown
    :title="sortedBy"
    :items="arrangements"
    @sort="sortByPrice"
  ></dropdown>
  <div>
    <h3>Products</h3>
    <ul>
      <li v-for="product in sortedProducts" :key="product.index">
        {{ product.name }} ${{ product.price }}
      </li>
    </ul>
  </div>
</template>

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

export default {
  components: {
    Dropdown,
  },

  data() {
    return {
      sortedBy: "Featured",
      arrangements: ["Featured", "Lowest", "Highest"],
      productData: {
        // dummy data for demo purposes
        products: [
          { index: 1, name: "product A", price: 1, featured: true },
          { index: 2, name: "product B", price: 2, featured: false },
          { index: 3, name: "product C", price: 6, featured: true },
          { index: 4, name: "product G", price: 4, featured: false },
          { index: 5, name: "product V", price: 0, featured: true },
        ],
      },
    };
  },
  methods: {
    sortByPrice: function (item) {
      this.sortedBy = item;
    },
  },
  computed: {
    sortedProducts: function () {
      if (this.sortedBy === "Featured") {
        return this.productData.products.filter((prod) => prod.featured);
      } else if (this.sortedBy === "Highest") {
        return this.productData.products.sort((a, b) => b.price - a.price);
      } else if (this.sortedBy === "Lowest") {
        return this.productData.products.sort((a, b) => a.price - b.price);
      }
      // the "just-in-case" default return
      return this.productData.products;
    },
  },
};
</script>

and the child Dropdown.vue component:

<template>
  <div class="dropdown">
    <button @click="toggleShow()" class="dropbtn">
      {{ title }}
    </button>

    <div v-if="showMenu" class="menu">
      <div
        class="menu-item"
        v-for="(item, index) in this.items"
        :key="index"
        @click="sortPrice(item)"
      >
        {{ item }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Dropdown-menu",

  props: {
    title: String,
    items: {
      type: Object,
      required: true,
    },
  },

  data() {
    return {
      showMenu: false,
    };
  },

  methods: {
    toggleShow: function () {
      this.showMenu = !this.showMenu;
    },

    sortPrice: function (item) {
      this.$emit("sort", item);
      this.toggleShow();
    },
  },
};
</script>
Advertisement