Search code examples
vue.jsvuejs2vuetify.jsvue-filter

Filter out duplicate data in Vue


How can I filter out duplicate tags in a list, so that only one of each tag is listed?

Here's my component code:

  <template>
    <v-layout row wrap>

    <ul>
      <li v-for="post in sortByName(posts)" :key="post.key">
       <v-chip
         v-for="i in sortByName(post.tags)"
         :key="i.key"
         :color="`${i.color} lighten-3`"
         label
         small
       >
          <span class="pr-2">
            {{ i.text }}
          </span>
        </v-chip>
      </li>
    </ul>

  </v-layout>
  </template>

  <script>
    import { mapState } from 'vuex'
    const fb = require('../firebaseConfig.js')

    export default {
      data: () => ({

      }),
      computed: {
        ...mapState(['posts'])
    },
    methods: {
      // function to put the tags in the right order to a -> z
      sortByName (list) {
        return _.orderBy(list, 'text', 'asc');
      }
     }
    }
  </script>

For example, in the screenshot below, I want to filter out Beach, so that I only see Beach once in the list:

screenshot

The data looks like this: data format


Solution

  • One solution is to use a computed property that returns a new list (e.g., filteredPosts) of posts with its tags array filtered. In the example below, a cache inside the computed property is used to track tags. The computed handler maps this.posts[] into a new Array and filters each entry's tags[], tracking new tags in the cache as "seen" and removing tags already "seen".

    template:

    <li v-for="post in filteredPosts" :key="post.key">
    

    script:

    computed: {
      filteredPosts() {
        const tagCache = {};
        const newPosts = this.posts.map(post => {
          return {
            ...post,
            tags: post.tags.filter(tag => {
              const seen = tagCache[tag.text];
              tagCache[tag.text] = true;
              return !seen;
            })
          };
        });
        return newPosts;
      }
    }
    

    new Vue({
      el: '#app',
      data() {
        return {
          posts: [
            {
              key: 1,
              tags: [
                { color: 'blue', text: 'Sky' },
                { color: 'green', text: 'Tree' },
                { color: 'yellow', text: 'Beach' },
              ],
            },
            {
              key: 2,
              tags: [
                { color: 'purple', text: 'Grape' },
                { color: 'red', text: 'Apple' },
                { color: 'orange', text: 'Orange' },
              ],
            },
            {
              key: 3,
              tags: [
                { color: 'blue', text: 'Blueberry' },
                { color: 'yellow', text: 'Beach' },
              ],
            },
            {
              key: 4,
              tags: [
                { color: 'pink', text: 'Flower' },
                { color: 'yellow', text: 'Beach' },
              ],
            },
          ]
        };
      },
      methods: {
        // function to put the tags in the right order to a -> z
        sortByName (list) {
          return _.orderBy(list, 'text', 'asc');
        },
      },
      computed: {
        filteredPosts () {
          const tagCache = {};
          // map `posts` to a new array that filters
          // out duplicate tags
          const newPosts = this.posts.map(post => {
              return {
                ...post,
                tags: post.tags.filter(tag => {
                  const seen = tagCache[tag.text];
                  tagCache[tag.text] = true;
                  return !seen;
                })
              };
            });
          return newPosts;
        }
      }
    })
    @import 'https://unpkg.com/vuetify@1.1.9/dist/vuetify.min.css'
    <script src="https://unpkg.com/vue@2.5.17"></script>
    <script src="https://unpkg.com/lodash@4.17.10/lodash.min.js"></script>
    <script src="https://unpkg.com/vuetify@1.1.9/dist/vuetify.min.js"></script>
    
    <div id="app">
      <v-app id="inspire">
        <v-container fluid class="pa-0">
          <v-layout row wrap>
            <ul>
              <li v-for="post in filteredPosts" :key="post.key">
                <v-chip v-for="i in sortByName(post.tags)"
                        :key="i.key"
                        :color="`${i.color} lighten-3`"
                        label
                        small>
                  <span class="pr-2">
                    {{ i.text }}
                  </span>
                </v-chip>
              </li>
            </ul>
          </v-layout>
        </v-container>
      </v-app>
    </div>