Building an Online Store with Vue CLI — Part 7

Creating a random outfit recommender

Nathan Magyar
5 min readMar 15, 2019

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 ids 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 ids 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 ids.

// 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. 👋

--

--

Nathan Magyar

User Experience Designer and Front End Developer, University of Michigan Office of Academic Innovation