3
\$\begingroup\$

I have built a Vue app which fetches data from an endpoint and presents that data in a table with sortable columns.

The app is based on this React challenge which is to create a table of users and their properties, from the data provided in the endpoint.

Here are the user stories fulfilled:

  1. I can see a table of the Free Code Camp campers who've earned the most brownie points in the past 30 days.
  2. I can see how many brownie points they've earned in the past 30 days, and how many they've earned total.
  3. I can toggle between sorting the list by how many brownie points they've earned in the past 30 days and by how many brownie points they've earned total.

Here is my code (working example):

const endpoint = 'https://fcctop100.herokuapp.com/api/fccusers/top/recent'

const store = {
  state: {
    sortingBy: ''
  }
}

new Vue({
  el: '#app',
  data() {
    return {
      data: [],
      state: store.state
    }
  },
  mounted() {
    Vue.http.get(endpoint).then(response => {
      this.data = response.body
    }, response => {
      console.warn('There was a data error:', response)
    })
  },
  methods: {
    sortByRecent() {
      this.data = _.sortBy(this.data, 'recent').reverse()
      store.state.sortingBy = 'recentDescending'
    },
    sortByAllTime() {
      this.data = _.sortBy(this.data, 'alltime').reverse()
      store.state.sortingBy = 'allTimeDescending'
    },
    getActiveClass(string) {
      return {active: store.state.sortingBy.includes(string)}
    }
  }
})
#app {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.active {
  font-weight: bold;
}
button {
  font-size: 20px;
}
img {
  border-radius: 50%;
  height: 30px;
  width: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.3/vue-resource.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

<div id="app">
  <button @click="sortByRecent">Sort by Recent Points</button>
  <button @click="sortByAllTime">Sort by All Time Points</button>
  <table>
    <thead>
      <tr>
        <td>#</td>
        <td></td>
        <td>Username</td>
        <td :class="getActiveClass('recent')">Recent Points</td>
        <td :class="getActiveClass('allTime')">Total Points</td>
      </tr>
    </thead>
    <tr v-for="(user, index) in data">
      <td>{{index + 1}}</td>
      <td>
        <img :src="user.img">
      </td>
      <td>{{user.username}}</td>
      <td>{{user.recent}}</td>
      <td>{{user.alltime}}</td>
    </tr>
  </table>
</div>

Here is a repo with all the code (bootstrapped with vue-loader webpack).

My questions are:

  1. I have placed a method for computing a CSS classname in methods - getActiveClass. In the documentation, it specifies that such methods can live inside the computed object:

    We can also bind to a computed property that returns an object.

    However when doing so, I get the following console error in the browser:

    Uncaught TypeError: _vm.getActiveClass is not a function

    Why does placing this method inside a computed object render this error?

  2. My project was bootstrapped with vue-loader's webpack preset. In order to make the API call to my endpoint, I had to import Vue so that I could use vue-resource. Should the API call be made in main.js and then passed in as a prop to App.vue to avoid importing Vue in multiple places?

  3. The data is received from the API call in any of the lifecycle methods beforeCreate, created, beforeMount and mounted. Which is the correct one for it to go?

  4. Does it make sense for this code to all live in one single file component, or should it be split up into separate smaller components?

\$\endgroup\$
3
\$\begingroup\$

I'm new to Vue myself so this may not be 100% accurate, just my own opinions. Kudos for learning and reaching out for feedback! You seem to have grokked a lot so I'm impressed.

  1. Computed properties are used the same as data properties. You do not call them like methods, and you can't pass parameters to computed properties. Instead of :class="getActiveClass('recent')" you'd have to use something like :class="{active: isSortedByRecentPoints}" or :class="recentPointsHeaderClasses". But what you have works just fine IMO.
  2. There is no penalty to importing Vue in multiple places, and it is actually good practice. Think of it like Java imports, you are declaring all of our external dependencies in your header so you know what kinds of operations this code is likely to perform without reading every single line.
  3. The correct hook is determined by your use case. For this example any one of the hooks mentioned would work fine. For more general advise, I tend to prefer the mounted hook, but that's just me and I don't know what I don't know. Will the component get mounted multiple times? Will you have all of the data you need to make the call at created? I'm not familiar enough with the details of Vue to give more in depth advise.
  4. For this example one Vue instance works just fine. If you want to experiment you can extract a true component from your code and create an API: The table data would be an input prop, and any actions on that data would be output events. I wouldn't consider sorting to be a reasonable output event. How about a view details event on the user?

If you're looking for extra credit I'd recommend looking into Vuex for your store. Currently you're mutating the store directly, which kind of goes against the whole point of using a store. Also a store is overkill for this example, but I'd recommend it for learning purposes. I'll give you a hint: Instead of using state in your data object you will probably want to bind directly to sortingBy and then use $store.commit to alter it in your methods. Commit will trigger a mutation to change the state, which will in turn update your component's data. You can do something similar for the table data - put it in a store and dispatch a fetchdata action to GET your data, then commit it to the state. This may seem like overkill at first (and it probably is for simple apps) until you start playing around with the time travelling debugger in the Vue devtools, hot module replacement, and the like. Super cool stuff.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.