Vue 3 List Filtering: Pokémon Edition

Vue 3 List Filtering: Pokémon Edition

Filtering lists is a super basic, but very important piece to almost any application. VueJS provides a nice property called computed that allows us to reactively update our UIs as data in the model changes. Let's take a look at this in action by building a searchable list of Pokémon!

The complete project can be found on github


Basic Setup

The code for this project was put together using the Vue CLI and all of the PokéData is from the PokéAPI. I've gone ahead and added some components and styling to make the final product a bit more interactive and visually appealing. We'll just focus on the filtering mechanism for now.

The main file we will be playing with here is App.vue, a basic Vue component that display's the Pokémon logo and a list of Pokémon. It imports a custom composition function I've created to handle grabbing our Pokémon list (plus some extra functionality for the final product). It also uses a PokéBall component to display each Pokémon.

<template>
    <div class="container">
        <img alt="Pokemon logo" src="./assets/pokemon-logo.svg" />
        <div class="pokemon-list">
            <PokeBall v-for="(poke, i) in pokemon" balltype="poke" :pokemon="poke" :key="i"/>
        </div>
    </div>
</template>

<script>
import { usePokemonList } from '@/composable/usePokemonList'
import PokeBall from '@/components/pokeball'

export default {
    name: "App",
    setup() {
        const { pokemon } = usePokemonList()

        return {
            pokemon
        }
    },
    components: {
        PokeBall
    }
}
</script>

<style scoped>
    @import 'app.css';
</style>

pokemon-list.gif


Handling Field Input

The goal here is to be able to filter down our list by name to make it easier to find what you are looking for.

NOTE: We are only grabbing 100 Pokés in this example. The final product on github has the ability to load more, but we won't worry about that in this tutorial

To do this, we'll first need some sort of UI to handle the input value we want to filter our list by. Let's add an input field <template> to handle this:

<template>
    <div class="container">
        <img alt="Pokemon logo" src="./assets/pokemon-logo.svg" />
        // Shiny new search bar with an input field
        <div class="search-bar">
            <input
                class="search-box"
                placeholder="Filter pokémon..."
            />
        </div>
        <div class="pokemon-list">
            <PokeBall v-for="(poke, i) in pokemon" balltype="poke" :pokemon="poke" :key="i"/>
        </div>
    </div>
</template>

Great! We have a nice-looking search bar, but it doesn't really do anything yet... We need to allow Vue access to the field's value in a reactive way so that we always know what the current value is and can filter our list accordingly. To do this, we'll need to create a property we'll call filterText and bind that value to the input field. Here's how:

In our <template>, we will add a v-model attribute to our input field with a value of the property name we want to bind the field's value to.

<input
    class="search-box"
    placeholder="Filter pokémon..."
    v-model="filterText"
/>

In our <script> section we need to create the filterText property and expose it to the template. This value needs to be reactive (to update automatically as the input field changes), so we will need to use Vue's ref property.

import { ref } from 'vue'
import { usePokemonList } from '@/composable/usePokemonList'
import PokeBall from '@/components/pokeball'

export default {
    name: "App",
    setup() {
        const { pokemon } = usePokemonList()
        const filterText = ref('')

        return {
            pokemon,
            filterText
        }
    },
    components: {
        PokeBall
    }
}

Here, we've imported the ref property from Vue and created a new ref called filterText in our setup function. In order for this to be available to the rest of the Vue component (template, computed properties, etc...) we need to add it to the setup()'s return object.

reactive-text-vue.gif

I've gone ahead and added some output above to demonstrate the reactivity of the new filterText property

Now that we've got our input taken care of, let's put it to use!


Filtering Our List

Vue has a handly little property called computed that suits our needs perfectly. computed values are functions that return a value that can be bound to the template just like a variable. The function is aware of its dependencies and their values, so as values change in the Vue component, if a value is a dependency of the computed variable the template will be updated in a reactive way.

What does that mean? It's a bit confusing without seeing it in action. Let's apply this to our Pokémon list:

import { ref, computed } from 'vue'
import { usePokemonList } from '@/composable/usePokemonList'
import PokeBall from '@/components/pokeball'

export default {
    name: "App",
    setup() {
        const { pokemon } = usePokemonList()
        const filterText = ref('')
        const filteredPokemon = computed( () => {
            let filter = filterText.value
            if (!filter.length) return pokemon.value
            return pokemon.value.filter( poke => 
                poke.name.toLowerCase().includes(filter.toLowerCase())
            )
        })
        return {
            pokemon,
            filterText,
            filteredPokemon
        }
    },
    components: {
        PokeBall
    }
}

Here we are creating a new "computed" property of the Vue component named filteredPokemon. This property has two dependencies, filterText and pokemon because these are both properties of the Vue component as well. As those properties change, this filteredPokemon property will update itself using the logic provided in the function and all of the bindings in the template will reflect the changes.

So in this case, as we type into the input field, causing an update to filterText, the filteredPokemon property will update using the new filter string.

NOTE: To access the values of our refs within the setup function, we need to access their .value properties. This isn't necessary within the <template>

To do the actual filter, we are using Javascript's Array method .filter(). This will go through each item in an array and run a function on each item. If the function returns true, it will keep the item, otherwise it will leave it out. The .filter() function returns a new array with the filtered results.

With this computed property completed, we can now bind our list to the filteredPokemon property instead of the pokemon property so that we only see the filtered results instead of the whole array.

<div class="pokemon-list">
    <!--- Note we are now binding to filteredPokemon --->
    <PokeBall v-for="(poke, i) in filteredPokemon" balltype="poke" :pokemon="poke" :key="i"/>
</div>

Now as we type into the input box, it should filter our results.

vue-filter-working.gif


Conclusion

Vue's computed property is a powerful tool that makes our job of reactive data-binding infinitely easier. Anything from generating lists, to transforming data, to applying classes/styling to elements on the page as properties change is possible with this single property, along with so many other uses.

The sample we built is just a basic list with some filtering, but I've added some other cool functionality in the completed project. If you'd like to take a look at this thing built out a bit more, check out the github link provided at the top of the page.

Thanks for reading, I hope it was the very best, like no tutorial ever was 🤓

... Okay, that was a bit cheesy, but 🤷

Go catch 'em all!

team-rocket.gif