1
\$\begingroup\$

I have three getters in my store:

getActiveTournaments(state) {
  return state.tournaments.data
    ? state.tournaments.data
      .filter(tournament => tournament.state_value === 'in_progress')
    // sort tournaments by date of creation
      .sort((tournamentA, tournamentB) => tournamentA.created_at - tournamentB.created_at)
    : [];
},
getFutureTournaments(state) {
  return state.tournaments.data
    ? state.tournaments.data
      .filter(tournament => tournament.state_value === 'promo')
    // sort tournaments by date of creation
      .sort((tournamentA, tournamentB) => tournamentA.created_at - tournamentB.created_at)
    : [];
},
getPastTournaments(state) {
  return state.tournaments.data
    ? state.tournaments.data
      .filter(tournament => tournament.state_value === 'finished')
    // sort tournaments by date of creation
      .sort((tournamentA, tournamentB) => tournamentA.created_at - tournamentB.created_at)
    : [];
},

In my component I have a slider which accept an array of three items, each first one from an arrays returned by every getters. So I created a computed property in my component in order to get these items and then pass them to the slider:

<template>
 ...
 <hovering-image-slider v-if="getLatestTournaments.length > 0"
                         :slideData="getLatestTournaments" />
 ...
</template>

<script>
 ...
 getLatestTournaments() {
  return this.getFutureTournaments[0]
  && this.getActiveTournaments[0]
  && this.getPastTournaments[0]
    ? [
      this.getFutureTournaments[0],
      this.getActiveTournaments[0],
      this.getPastTournaments[0]]
    : [];
},
</script>

It works fine but it seems like an ugly solution. Slider component will get an error of undefined data if I will not check the presence of [0] element in the computed property. I thought that by doing some checking at the level of my getters, it will not require checking in the component but apparently it does. Can you please advice how I can improve this existing code?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ If you would add console.log(state.tournaments.data) in your getters, what do they say? \$\endgroup\$ – Simon Forsberg Mar 28 '19 at 10:16
  • \$\begingroup\$ It will return all tournaments I have. I send one request to get them all and then filter this state in getters in order to get tournament of one type. \$\endgroup\$ – Olga B Mar 28 '19 at 10:21
4
\$\begingroup\$

DRY the code

The 3 store getters are identical apart from the filter value. So first thing is to remove the repeated code. Create a function (either in the store, or within its scope, that you pass the state and the filter string.

You get something like

filterTournaments(state, filter) {
  return state.tournaments.data
    ? state.tournaments.data
      .filter(tournament => tournament.state_value === filter)
      .sort((A, B) => A.created_at - B.created_at)
    : [];
},    
getActiveTournaments(state) { return this.filterTournaments(state, "in_progress") },
getFutureTournaments(state) { return this.filterTournaments(state, "promo") },
getPastTournaments(state) { return this.filterTournaments(state, "finished") },

Direct to the array

The current solution on the HTML side is ugly, if you do have data to display, you have to get the data again, filtering and sorting each a second time.

Get the three items and put them directly in an array and test if all truthy, if so return the array, else an empty array.

getLatestTournaments() {
  const tournaments = [
      this.getActiveTournaments[0], 
      this.getFutureTournaments[0], 
      this.getPastTournaments[0]
  ];
  return tournaments[0] && tournaments[1] && tournaments[2] ? tournaments : [];
}

Do it in the store

Fast and efficient

If you want it done at the store and you can add some smarts to the filter function, that way you can avoid a lot of needless processing and memory use. Using filter and sort to find the min item with a matching state_value is not efficient.

getFilteredTournaments(state) {        
    const data = state.tournaments.data;
    if (!data) { return [] }
    const filters = ["in_progress", "promo", "finished"];
    const tournaments = [];
    for (const filter of filters) {
        let min = Infinity, minTournament;
        for (const tournament of data) {
            const {state_value, created_at} = tournament;
            if (state_value === filter && created_at < min) {
               min = created_at;
               minTournament = tournament;
            }
        }
        if (minTournament) { tournaments.push(minTournament) }
        else { return [] }
    }
    return tournaments;
}, 

Then you need only the one call

:slideData="getFilteredTournaments"

I am not that familiar with VUE so may need the wrapper

:slideData="getLatestTournaments"

<script>
   getLatestTournaments() { return this.getFilteredTournaments }
</script>

But that type of code does not suit everyone.

Keep it simple

Using the existing methodologies you can do the 3 filters in one function in the store as follows

getAllTournaments(state) { 
    const data = state.tournaments.data;
    if (data) {
        const filter = filter => data
          .filter(tournament => tournament.state_value === filter)
          .sort((A, B) => A.created_at - B.created_at)[0];
        const result = [
            filter("in_progress"),
            filter("promo"),
            filter("finished"),
        ];
        return result.some(item => !item) ? [] : result;
    }
    return [];
},

and call with

getLatestTournaments() { return this.getAllTournaments }
\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much for this wonderful explanation and solution! It works! :) \$\endgroup\$ – Olga B Mar 28 '19 at 15:40

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.