The purpose of this post is to show a simple way on how to make a small search engine with a debounce effect.
Such a project can be extended in many ways, but I will try to make it something basic but efficient.
π¨ Note: This post requires you to know the basics of React with TypeScript (basic hooks and fetch requests).
Any kind of Feedback is welcome, thanks and I hope you enjoy the article.π€
βΆοΈ Vanilla CSS (You can find the styles in the repository at the end of this post)
Β
π What is the "Debounce" effect?
The debounce effect is when they are not executed at the time of their invocation. Instead, their execution is delayed for a predetermined period of time. If the same function is invoked again, the previous execution is cancelled and the timeout is restarted.
Β
π Creating the project.
We will name the project: search-debounce (optional, you can name it whatever you like).
npm init vite@latest
We create the project with Vite JS and select React with TypeScript.
Then we execute the following command to navigate to the directory just created.
cd search-debounce
Then we install the dependencies.
npm install
Then we open the project in a code editor (in my case VS code).
code .
Β
π First steps.
Inside the folder src/App.tsx we delete all the contents of the file and place a functional component that displays a title.
Now we create the folder src/components and inside the folder we create the file Input.tsx and inside we add the following:
exportconstInput=()=>{return(<><labelhtmlFor="pokemon">Name or ID of a Pokemon</label><inputtype="text"id="pokemon"placeholder="Example: Pikachu"/></>)}
π¨ Note: This is NOT the only way to perform this exercise, it is only one option! If you have a better way, I would like you to share it in the comments please. π
In this case I am going to handle the input status at a higher level, i.e. the App component of the App.tsx file.
We will do this, because we need the value of the input available in App.tsx, since the request to the API and the debounce effect will be made there.
1 - First we create the state to handle the value of the input.
const[value,setValue]=useState('');
2 - We create a function to update the state of the input when the input makes a change.
This function receives as parameter the event that emits the input, of this event we will obtain the property target and then the property value, which is the one that we will send to our state.
And so we already have the status of our input under control. π₯³
Β
π Creating the function for the API request.
Now we create the src/utils folder and inside we place a file called searchPokemon.ts and add the following function to make the request, and search for a pokemon by its name or ID.
π¨ Note: The API response has more properties than what is represented in the ResponseAPI interface.
This function receives two parameters:
pokemon: is the name or ID of the pokemon.
signal**: allows to set event listeners. In other words, it will help us to cancel the HTTP request when the component is unmounted or makes a change in the state.
This function returns the pokemon data if everything goes well or null if something goes wrong.
Now, we create an effect so that when the value of the input changes, we execute the setTimeout function that will update the state of the debouncedValue sending the new value of the input, after 1 second, and thus we will obtain the keyword or the pokemon, to make the request to the API.
At the end of the effect, we execute the cleaning method, which consists of cleaning the setTimeout function, that is why we store it in a constant called timer.
Once we have the value of the input already with the debounce effect, it is time to make the API call.
For that we will use the function that we created previously, searchPokemon.tsx.
For it, we are going to use an effect.
First we create the controller which is the one that will help us to cancel the HTTP request, as we mentioned before.
Inside the controller we have two properties that interest us:
abort(): when executed, cancels the request.
signal**: maintains the connection between the controller and the request to know which one to cancel.
The abort() is executed at the end, when the component is unmounted.
The dependency of this effect will be the value of the debouncedValue, since every time this value changes, we must make a new request to search for the new pokemon.
And since the searchPokemon function returns a promise and within the effect it is not allowed to use async/await, we will use .then to resolve the promise and get the value it returns.
It should look like this when there are no results π:
It should look like this there is a pokemon π:
4 - And now finally, we add a last condition, where we evaluate if the pokemon exists (i.e. it is not null) and if it is an empty object we return a fragment.
This is because the initial state for storing pokemon will be an empty object. "{}".
If we don't put that condition, then at the start of our app, even without having typed anything in the input, the "No results" message will appear, and the idea is that it will appear after we have typed something in the input and the API call has been made.
Now inside the effect where we make the call to the API through the function searchPokemon, before making the call we send the value of true to the setIsLoading to activate the loading.
Then, once we get the data inside the .then we send the data to the setPokemon (which can be the pokemon or a null value).
And finally we send the value of false to setIsLoading to remove the loading.
That's a lot of logic in one component right? π±
Now it's our turn to refactor!
Β
π Cleaning the logic of our component.
We have a lot of logic in our component so it is necessary to separate it into several files:
Logic to control the input.
Debounce logic.
Logic to make the API call and handle the pokemon.
And as this logic makes use of hooks like useState and useEffect, then we must place them in a custom hook.
The first thing will be to create a new folder src/hooks.
1. Handling the logic to control the input.
Inside the folder src/hooks we create the following file useInput.ts**.
And we place the logic corresponding to the handling of the input.
Inside the folder src/hooks we create the following file useSearchPokemon.ts.
We place the logic related to make the request to the API and show the pokemon.
This custom hook receives as parameter a string called search, which is the name of the pokemon or the ID. And we send that parameter to the function that makes the API call searchPokemon.
π¨ Note: Observe the If part in the effect, at the end we place an else where if the debouncedValue is empty, we will not make a call to the API and we send the value of an empty object to setPokemon.
Inside the folder src/hooks we create the following file useDebounce.ts and place all the logic to handle the debounce effect.
This custom hook, receives 2 parameters:
value: is the value of the input status.
delay**: is the amount of time you want to delay the debounce execution and is optional.
π¨ Note: the delay property is used as the second parameter of the setTimeout function, where in case delay is undefined, then the default time will be 500ms.
And also, we add the delay property to the effect dependencies array.