Last week, I had a task to build a keyword search feature on my job. It's required a constant background checking whenever a new keyword was typed. The problem is I don't want to continually request the API; the new keyword was changed. It will hurt our server. Let's start building a component first.
What we're going to build is a search UI that allows users to find any information about Star Wars by name.
Build an Input component
Let’s start with basic input. I’m going to create a search input that accepts name
.
<template>
<div>
<div class="w-full px-3 mb-6 md:mb-0">
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="name"
>
Star wars name
</label>
<div class="relative mb-3">
<input
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
id="name"
/>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
>
<svg
class="h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</div>
</div>
<button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Search
</button>
</div>
</div>
</template>
Let’s add the v-model
to the form data and method
for checking API.
<template>
<div>
<div class="w-full px-3 mb-6 md:mb-0">
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="name"
>
Star wars name
</label>
<div class="relative mb-3">
<input
v-model="keyword"
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
id="name"
/>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
>
<svg
class="h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</div>
</div>
<button
@click.prevent="checkName"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Search
</button>
</div>
</div>
</template>
<script>
export default {
data: () => ({
keyword: "",
}),
methods: {
checkName() {
console.log(`Checking name: ${this.keyword}`);
},
},
};
</script>
Prepare the API
I’ve going to use an external API to demonstrate this feature. We’re going to use SWAPI API to test it.
This endpoint we’re going to use. The search
query parameter is for our search keyword.
https://swapi.dev/api/people/?search=luke
The endpoint above will give the list of Star Wars with Luke's name.
This is the result. You may use any REST client to test it.
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "http://swapi.dev/api/planets/1/",
"films": [
"http://swapi.dev/api/films/1/",
"http://swapi.dev/api/films/2/",
"http://swapi.dev/api/films/3/",
"http://swapi.dev/api/films/6/"
],
"species": [],
"vehicles": [
"http://swapi.dev/api/vehicles/14/",
"http://swapi.dev/api/vehicles/30/"
],
"starships": [
"http://swapi.dev/api/starships/12/",
"http://swapi.dev/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "http://swapi.dev/api/people/1/"
}
]
}
Let’s integrate our Vue component with the API
We’re going to use axios
to consume the API. Run this command to install it.
npm install axios
Whenever the user clicks the submit button, send the request to the API, and display the result.
Let’s update our checkName
methods.
<script>
import axios from "axios";
export default {
data: () => ({
keyword: "",
}),
methods: {
checkName() {
console.log(`Checking name: ${this.keyword}`);
axios
.get("https://swapi.dev/api/people/", {
params: {
search: this.keyword,
},
})
.then((res) => {
console.log(res.data.results);
})
.catch((err) => {
console.log(err);
});
},
},
};
</script>
Nice one!
Let’s list out the result in our UI.
Add new data state to store the result; let’s called it peoples
. After the request successful, we store the result in peoples
state.
export default { data: () => ({ keyword: "", peoples: [], }), methods: {
checkName() { console.log(`Checking name: ${this.keyword}`); axios
.get("https://swapi.dev/api/people/", { params: { search: this.keyword, }, })
.then((res) => { console.log(res.data.results); this.peoples = res.data.results;
}) .catch((err) => { console.log(err); }); }, }, };
And this is the snippet for UI to show a list of peoples.
<ul class="px-3 list-disc">
<li v-for="people in peoples" :key="people.url">
{{ people.name }} - Height: {{ people.height }} Mass: {{ people.mass }}
</li>
</ul>
Listen to the keyword.
Since we’re going to whenever the user typed the keyword, let’s add a listener to the keyword
search. I’m going to add a keyword
watcher in our component.
Whenever the keyword
field changed, it will trigger the watcher. Thank god the watcher helps a lot.
watch: { keyword(newKeyword,oldKeyword) { console.log(`New keyword is
${newKeyword}`); console.log(`Old keyword is ${oldKeyword}`) } }
Whenever the keyword is changing, it will call the watcher method. Let’s call the checkName
method in the watcher.
watch: { keyword(newKeyword,oldKeyword) { console.log(`New keyword is
${newKeyword}`); console.log(`Old keyword is ${oldKeyword}`); this.checkName();
} }
It’s working!
But here comes the problem, we don’t want to keep sending the request to the backend whenever use typed. It may hurt our server.
Delay the request
We need to delay the request and send the request after one second the user finish typing.
Thank god. There is a lodash for the utility library. There is a method in lodash called debounce
. It's delayed the request, and it canceled the previous request if there is a new one.
It may save several requests called and provide a good experience to the user interface.
Let's install loadash
first. Run npm install lodash
and import the debounce
methods in our vue file.
<script>
import axios from "axios"; import {debounce} from "lodash"; …
</script>
Initialize the debounce method during the created lifecycle. We passed the methods name and time delay. For this example, we will trigger the request after one second.
created() { this.debounceName = debounce(this.checkName, 1000); }
For our watcher method, we will replace it with a new function that we just created.
watch: {
keyword() {
if (!this.keyword) return;
this.debounceName();
}
}
Finally, its working 😄.
Now, the API will only be request after user finish typing.
Source Code
<template>
<div>
<div class="w-full px-3 mb-6">
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="name"
>
Star wars name
</label>
<div class="relative mb-3">
<input
v-model="keyword"
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
id="name"
/>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
>
<svg
class="h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</div>
</div>
<button
@click.prevent="checkName"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Search
</button>
</div>
<ul class="px-3 list-disc">
<li v-for="people in peoples" :key="people.url">
{{ people.name }} - Height: {{ people.height }} Mass: {{ people.mass }}
</li>
</ul>
</div>
</template>
<script>
import axios from "axios";
import { debounce } from "lodash";
export default {
data: () => ({
keyword: "",
peoples: [],
}),
methods: {
checkName() {
// eslint-disable-next-line no-console
console.log(`Checking name: ${this.keyword}`);
axios
.get("https://swapi.dev/api/people/", {
params: {
search: this.keyword,
},
})
.then((res) => {
// eslint-disable-next-line no-console
console.log(res.data.results);
this.peoples = res.data.results;
})
.catch((err) => {
// eslint-disable-next-line no-console
console.log(err);
});
},
},
created() {
this.debounceName = debounce(this.checkName, 1000);
},
watch: {
keyword() {
if (!this.keyword) return;
this.debounceName();
},
},
};
</script>
<style scoped></style>
You can view the source code in my github - Source Code
Thanks for reading my article. Let me know if you have any idea, what should I write on my next article.