I am trying to implement debouncing in my app, however, the most I am able to achieve is to debounce the speed of the input. The gist of the App, is that it first takes input from the user, to generate a list of cities, according to the input and when selected, will provide a forecast for the whole week.
Here is the original code, without debouncing:
import React, { useState, useEffect } from 'react';
import * as Style from './Searchbar.styles';
import { weatherAPI } from '../API/api';
import endpoints from '../Utils/endpoints';
import { minCharacters } from '../Utils/minChars';
import MultiWeather from './MultiCard';
import useDebounce from '../Hooks/useDebounce';
export default function SearchBar() {
const [cityName, setCityName] = useState('');
const [results, setResults] = useState([]);
const [chooseCityForecast, setChooseCityForecast] = useState([]);
useEffect(() => {
if (cityName.length > minCharacters) {
loadCities();
}
}, [cityName, loadCities]);
//this is used to handle the input from the user
const cityValueHandler = value => {
setCityName(value);
};
//first API call to get list of cities according to user input
const loadCities = async () => {
try {
const res = await weatherAPI.get(endpoints.GET_CITY(cityName));
setResults(res.data.locations);
} catch (error) {
alert(error);
}
};
//second API call to get the forecast
const getCurrentCity = async city => {
try {
const res = await weatherAPI.get(endpoints.GET_DAILY_BY_ID(city.id));
console.log(res);
setChooseCityForecast(res.data.forecast);
setCityName('');
setResults([]);
} catch (error) {
alert(error);
}
};
return (
<Style.Container>
<h1>Search by City</h1>
<Style.Search>
<Style.SearchInner>
<Style.Input type="text" value={cityName} onChange={e => cityValueHandler(e.target.value)} />
</Style.SearchInner>
<Style.Dropdown>
{cityName.length > minCharacters ? (
<Style.DropdownRow results={results}>
{results.map(result => (
<div key={result.id}>
<span
onClick={() => getCurrentCity(result)}
>{`${result.name}, ${result.country}`}</span>
</div>
))}
</Style.DropdownRow>
) : null}
</Style.Dropdown>
</Style.Search>
{chooseCityForecast && (
<section>
<MultiWeather data={chooseCityForecast} />
</section>
)}
</Style.Container>
);
}
The code above works perfectly, aside from creating an API call everytime I add an additional letter. I have refered to this thread on implementing debouncing. When adjusted to my code, the implementation looks like this:
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
}
};
const debouncedHandler = useCallback(debounce(cityValueHandler, 200), []);
But, as mentioned before, this results in debouncing/delaying user input by 200ms, while still creating additional API calls which each extra letter.
If I try to debounce the loadCities function or add a setTimeout method, it will delay the function, but will still make the API calls with each additional letter.
I have a hunch, that I need to remake the logic, which is handling the input, but at this point I am out of ideas.