Building an Online Store with Vue CLI — Part 7
Creating a random outfit recommender
Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 :
Previously we implemented a flexible GenderOverview.vue
component that displays products in our store for a given gender. In this tutorial we’ll add a new feature to the same component that randomly recommends an outfit for the user!
Step 1: Update store.js
In order to make this feature worthwhile, we should have a few more products in our online store. Let’s add them. The full collection of product photos (including the new ones for this tutorial) can be downloaded from this Dropbox folder. Copy/paste the following as your new store.js
file:
Step 2: Add data infrastructure
Our goal is to display a random shirt, pair of pants, and pair of shoes on each gender overview page. Since we’ll be potentially shuffling/updating each recommended product, it makes sense to store these products as data properties in our GenderOverview.vue
component. But, just like with our cart
array in store.js
, we don’t need to store the entire product object; just its id
. Later we’ll use an existing getter to retrieve the product object by that id
. We’ll call the shirt, pant and shoe id
s randomTopId
, randomBottomId
, and randomFootwearId
, respectively.
Inside GenderOverview.vue
create a data function that returns an object with three key/value pairs, all initialized to null
:
<script>
export default {
....,
data () {
return {
randomTopId: null,
randomBottomId: null,
randomFootwearId: null
}
},
...
}
</script>
Step 3: Create a DRY method for generating the above IDs
So we know we need 3 id’s: a randomTopId
, a randomBottomId
, and a randomFootwearId
. In each case, we’re retrieving a random product within a certain category. Since we’re fetching similar information in the same way each time, we can write one method and use it for all of them. Let’s call this method randomProductIdByCategory
, because that’s what we’re doing: we’re returning a random product ID according to a specific category. The categories will be “Shirts”, “Pants”, and “Shoes” because those are the strings found as the values to the category
property in the product
objects in store.js
. For example:
// store.js...
{
name: "Eugenia Kim | Carina Bow Bootie",
id: 53385,
...,
category: "Shoes",
...
},
...
Let’s write the method. Start by creating a methods
section in GenderOverview.vue
. In it, add the randomProductIdByCategory
function, which will accept one argument, a category
string (like “Shoes”). Inside the function, filter productsByGender
, a computed property that already exists in the GenderOverview
component, to only show products that belong to the given category
. We’ll call this filtered array allProductsInCategory
. Next, generate a randomIndex
that is between 0 and however many products there are in allProductsInCategory
. Finally, return the id
of a random product by indexing into allProductsInCategory
using randomIndex
:
// GenderOverview.vue...
methods: {
randomProductIdByCategory(category) {
let allProductsInCategory = this.productsByGender.filter(p => p.category === category);
let randomIndex = Math.floor(Math.random() * allProductsInCategory.length);
return allProductsInCategory[randomIndex].id;
},
},
...
Step 4: Use this method to populate the component data
We have a method for getting a random product id for each category, now let’s use it! The following approach might not be what you expected, though. Instead of replacing our null
values with something like:
randomTopId: this.randomProductIdByCategory('Shirts')
we will leave the data properties as set to null
and use the created()
lifecycle hook instead. Why? Because we need access to the component’s reactive data to set these properties. If we took the above approach, we’d get an error saying
Error in data(): "TypeError: Cannot read property 'filter' of undefined"
because when we call randomProductIdByCategory()
to get our random id’s, Vue is going to try to access/filter productsByGender
before it has access to the component’s data. We need to wait for that access. Hence our use of the created()
method, which will run only after the component is created.
If none of the above makes sense, just know that it’s an order-of-operations issue that results in us having to find a workaround. In this case, that workaround involves the
created()
lifecycle hook.
Just after your mixins
import, add the created()
lifecycle hook. Inside that function, update randomTopId
, randomBottomId
, and randomFootwearId
to store the returned value from randomProductIdByCategory
with the appropriate category string:
// GenderOverview.vue<script>
...
export default {
...,
mixins: [ imagePath ],
created() {
this.randomTopId = this.randomProductIdByCategory('Shirts');
this.randomBottomId = this.randomProductIdByCategory('Pants');
this.randomFootwearId = this.randomProductIdByCategory('Shoes')
},
data() {
...
},
...
}
</script>
Step 5: Transform ids into product objects
We now have 1 random product id
for each category, but we ultimately want to display each product’s image, name, and price. This means we need access to each product’s object. Let’s use computed properties and an existing getter
to transform the id
s into product objects.
Inside the computed
section of GenderOverview.vue
, add three new computed properties, each of which passes a corresponding top/bottom/footwear id
to the product
getter (that getter will use the id
to return the correct product object):
// GenderOverview.vue...
computed: {
gender() {
return this.$route.params.gender
},
pageTitle() {
return `${this.gender[0].toUpperCase()}${this.gender.slice(1)}`
},
productsByGender() {
return this.$store.getters.productsByGender(this.gender)
},
randomTop() {
return this.$store.getters.product(this.randomTopId)
},
randomBottom() {
return this.$store.getters.product(this.randomBottomId)
},
randomFootwear() {
return this.$store.getters.product(this.randomFootwearId)
}
},
...
Step 6: Update the template
Lines 13–34 contain the new mark-up for the “Our Recommendations” section, which consists mostly of three subsections, one for each random item. Each subsection displays the corresponding item’s image, name, and price, as well as links to the item’s detail page via the router-link
element. Don’t forget to add the CSS styles!
Step 7: Add shuffling functionality
Receiving one random outfit recommendation is okay, but it would be better if the user could “shuffle” the products to get unlimited recommendation combinations.
The template already contains a “shuffle” button that, when clicked, calls a method named recommendRandomOutfit
. This method doesn’t exist yet though, so let’s add it to the methods
section.
Inside recommendRandomOutfit()
we want to reassign randomTopId
, randomBottomId
, and randomFootwearId
to have new product id
s.
// GenderOverview.vue...
methods: {
...,
recommendRandomOutfit() {
this.randomTopId = this.randomProductIdByCategory('Shirts');
this.randomBottomId = this.randomProductIdByCategory('Pants');
this.randomFootwearId = this.randomProductIdByCategory('Shoes');
}
},
...
If the above code looks familiar, it’s because we’re repeating the same statements from the created()
method. Let’s “dry out” our code by replacing the current contents of created()
with calling recommendRandomOutfit()
:
// GenderOverview.vue...
mixins: [ imagePath ],
created() {
this.recommendRandomOutfit()
},
data () {
...
}
...
And that’s it! We now have a larger collection of products and a snazzy random recommender tool on each gender overview page!
In the next section we’ll work on getting this project deployed and hosted on the web. 👋