Vue 2 list band members by instrument name but only display the instrument name once

Viewed 42

I am able to list the band members by instrument name, but want to display the instrument name only once, instead of showing the instrument name for each person in the bassoon group, for instance. My thought was to extract a set of distinct instrument names, and do a v-if comparing the member's instrument name to the iterated set value, and only display the member if the member's data property instrumentName matches the current value of the instrument names set. With the code below, I'm getting an error "instNames is not defined". I'm still learning Vue and Nuxt, and am trying to get away from WebMatrix, the original platform for my website. The other thing I need is to attach an instrumentFilename (picture) to each distinct instrumentName. Any help is greatly appreciated!

<template>
  <div class="block bg-blue-200 w-96 p-6 m-auto">
    <table>
      <tr v-for="inst in instNames">
        <td>{{ inst.instrumentName }} <img :src="'/images/' + inst.instrumentFilename" >      
        <td v-for="member in members" :key="member.id">      
          <div v-if="member.instrumentName == inst.instrumentName"> </div>
        
          <div>{{ member.firstname }} {{ member.lastname }} </div>
          <div v-if="$auth.loggedIn" class="block">
            <p v-if="!member.email == ' '" class="ml-10"> Email: {{ member.email }} </p>
            <p v-if="!member.telephone == ' '" class="ml-10"> Home: {{ member.telephone }} </p>
            <p v-if="!member.telephone2 == ' '" class="ml-10">Cell: {{ member.telephone2 }}</p>
            <p v-if="!member.street == ' '" class="ml-10">Address: {{ member.street}}</p>          
            <p v-if="!member.city == ' '" class="ml-28">{{ member.city}}, {{ member.zip}}</p>
          </div>
        </td>
      </tr>
    </table>      
  </div>
  
</template>

<script>
export default {
  async asyncData(context){
    const { data } = await context.$axios.get('/api/members')        
    return {
      members : data     
    }
  },
 
  computed: {
    instNames: function() {      
      instNames = [...new Set(this.members.map(x => x.instrumentName))]
      return instNames
    } 
  } 
}
</script>
2 Answers

The above comments were invaluable, so a million thanks. I still had trouble with the uniqueness of my return array though; apparently, a Set counts multiple references to an object as unique. So I wound up using this code:

computed: {
    instNames: function() {      
    
      const instName = this.members.map
      (({ instrumentName, instrumentFilename }) => ({ instrumentName, instrumentFilename}))
      const instNameArray = []
      const instNameSet = new Set()
      for (const object of instName) {
        const objectJSON = JSON.stringify(object)
        if(!instNameSet.has(objectJSON)) {
          instNameArray.push(object)
        }
        instNameSet.add(objectJSON)
      }
      console.log(instNameArray)
         
      return instNameArray
    } 
  } 

Your computed property tries to assign instNames, but that's not declared anywhere. However, you don't really need the local variable because it's not used. You can just return the calculated set:

export default {
  computed: {
    instNames: function() {
      //instNames = [...new Set(this.members.map(x => x.instrumentName))]
      //^^^^^^^^^ ❌ not declared

      // Option 1: Declare it with `let` or `const`
      const instNames = [...new Set(this.members.map(x => x.instrumentName))]
      return instNames

      // Option 2: Just return the calculation
      return [...new Set(this.members.map(x => x.instrumentName))]
    } 
  } 
}

But your template tries to render instrumentName and instrumentFilename off of the computed instNames (an array of strings, not objects). Assuming those values are from this.members, you could update the computed value to return those fields:

export default {
  computed: {
    instNames: function() {
      const instName = this.members.map(x => ({
        instrumentName: x.instrumentName,
        instrumentFilename: x.instrumentFilename,
      }))

      // remove duplicates here ...
      
      return instName
    } 
  } 
}

Lastly, your template v-if conditions aren't properly checking for non-empty strings. For !member.email == ' ', the ! operator has precedence, and it checks member.email for a falsy value, which is the empty string for String. Then it compares it to a single space, which would always be false because a space is not falsy. You can replace that entire comparison with a simple check for a truthy value:

<p v-if="member.email">...</p>
<p v-if="member.telephone">...</p>
<p v-if="member.telephone2">...</p>
<p v-if="member.street">...</p>
<p v-if="member.city">...</p>
Related