How to build an autocomplete field with Vue 3

How to build an autocomplete field with Vue 3

Create an autocomplete/auto-suggest search field with Vue 3 and the Composition API without using any external packages

September 2021

Intro

Although Vue 3 was released almost a year ago many packages have not been updated to be compatible.

I typically reach for Kraaden Autocomplete or autoComplete.js which can be hooked into the Vue lifecycle methods and work great, however sometimes more flexibility is required, and I prefer using Vue specific packages where possible.

This approach uses Vue 3 with the built-in Composition API, doesn't use an external JS library and can be modified to suit most needs.

Tailwind CSS is used for basic styling which I will not be covering, and I have removed styling from the code snippets to keep the article concise. Vite is used for local development (none of these are required). A repo can be found at the bottom of the article.

Creating the text input

To start we will need to create a standard label and text input.

<label for="search">
  Type the name of a country to search
</label>

<input type="text" id="search" placeholder="Type here...">

You will have something like this:

Created label and input

Getting the search term

At the moment the input doesn't do anything. To move on we will need to get the value of the input when the user types something into it. Add this to the bottom of your Vue file:

<script>
import {ref} from 'vue'

export default {
  setup() {
    let searchTerm = ref('')

    return {
      searchTerm
    }
  }
}
</script>

The setup component option is new in Vue 3. Many functions and options can be set in here, rather than being spread throughout the file with the old Options API.

ref is a new function in Vue 3 used to make a variable reactive. To get or set a variable you will need to access the 'value' property of it.

What we have done above is initialised a reactive 'searchTerm' variable to an empty string, and returned it which will make it available in the template itself.

Now that we have prepared the script to accept the user's input, we now need to provide/bind it to the variable using v-model:

<input
  type="text"
  id="search"
  placeholder="Type here..."
  v-model="searchTerm"
>

The value of the 'searchTerm' variable will now always match the value of the input.

Searching the data

In this case I am using a JSON file which lists all the countries in the world. This will be available in the repo link at the bottom of this article.

The JSON file is imported and made available to the template:

import countries from './data/countries.json'

return {
  countries,
  searchTerm
}

To perform the search we will create the function below:

import {ref, computed} from 'vue'

const searchCountries = computed(() => {
  if (searchTerm.value === '') {
    return []
  }

  let matches = 0

  return countries.filter(country => {
    if (
      country.name.toLowerCase().includes(searchTerm.value.toLowerCase())
      && matches < 10
    ) {
      matches++
      return country
    }
  })
});

You can see above I have imported another function from Vue; computed, which is much the same as in Vue 2. It allows us to create cached methods/functions which will only update when something relevant to the function is updated.

If the search term is empty then an empty array is returned. If the search term has a value, filter is used to loop through all countries and returns those that contain the search term. I am converting everything to lowercase to help provide more accurate matching, and only showing a maximum of 10 results.

Showing the results

Update the template with the following to show the matching countries:

<ul v-if="searchCountries.length">
  <li>
    Showing {{ searchCountries.length }} of {{ countries.length }} results
  </li>
  <li
    v-for="country in searchCountries"
    :key="country.name"
  >
    {{ country.name }}
  </li>
</ul>

v-if is used to conditionally hide or show the list of results. This is based on the number of results returned from our previously created 'searchCountries' function. v-for is then used to loop through and display every matching result as a list item.

Selecting a result

The autocomplete is now functional in that it searches/filters the given data-set based on the user's input. However this isn't very useful if the user can't select a matching option.

To prepare for the user selecting a matching option we should add the following:

const selectCountry = (country) => {
  selectedCountry.value = country
  searchTerm.value = ''
}
 
let selectedCountry = ref('')

return {
  countries,
  searchTerm,
  searchCountries,
  selectCountry,
  selectedCountry
}

With the above added, we now have a 'selectCountry' method which can be called in the template. This will set the value of a new 'selectedCountry' reactive variable. Attach this to a click event in the template and provide the name of the clicked option to the function:

<li
  v-for="country in searchCountries"
  :key="country.name"
  @click="selectCountry(country.name)"
>
  {{ country.name }}
</li>

Now when the user selects a country it will be saved to the 'selectedCountry' variable, and the search term will be reset to an empty string which will then close the matching country list.

One final thing to wrap up is to show a confirmation of the user's selected choice:

<p v-if="selectedCountry">
  You have selected: {{ selectedCountry }}
</p>

Result

Using the autocomplete search


Sign up for my newsletter

Get notified when I post a new article. Unsubscribe at any time, and I promise not to send any spam :)

© Steven Cotterill 2021