Vue 3 List Filtering: Pokémon Edition
Tags: vue, javascript, functional, webdev
Published: 2021-05-13 05:25:02

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](https://github.com/sabinadams/Vue-Pokemon-List)*

---

## Basic Setup


The code for this project was put together using the [Vue CLI](https://cli.vuejs.org/) and all of the PokéData is from the [PokéAPI](https://pokeapi.co/). 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.

```javascript
<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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcurq3k70g6a1tfpkvkn.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:

```javascript
<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.
```html
<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`](https://v3.vuejs.org/guide/composition-api-introduction.html#reactive-variables-with-ref) property. 

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/he24y50hjrmv699b6b0b.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`](https://v3.vuejs.org/guide/computed.html#computed-properties) 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:

```javascript
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 `ref`s 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()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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.

```html
<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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ex90r620i91ygalv7o6k.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!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xgepgnntuq9pg0nv49x7.gif)

P.S. If you liked this article, be sure to follow me on [Twitter](https://twitter.com/sabinthedev) to get updates on new articles I write!