I'm using ReactSearchAutocomplete to display a list of 10 suggested names from a mySql query. The query is performed with a 100ms delay on user input, and it works fine but I'm clearly not handling the state properly because the query will return results fine, but upon calling setState, the search bar does not provide suggestions as would be expected.
Only if I then continue typing and trigger another query do the search results show up, and they seemingly are the results of the previous query.
My question is, how do I get the autocomplete component to properly render the suggested results from my query only AFTER setting the state of the namesList?
const SearchBar = () => {
const [namesList, setNamesList] = useState([]);
const handleOnSearch = (searchTerm) => {
if (searchTerm.length < 2) {
return;
}
let queryString = "url/api/search/" + searchTerm;
if (isDev) {
queryString = "http://localhost:3001/search/" + searchTerm;
}
fetch(queryString)
.then((response) => response.json())
.then((json) => {
let searchResults = json.map((o) => ({ id: o.name, name: o.name }));
setNamesList(searchResults);
})
.catch((error) => console.error(error));
};
return (
<div className="search-bar">
<ReactSearchAutocomplete
items={namesList}
onSearch={handleOnSearch}
autoFocus={true}
inputDebounce={100}
showNoResults={false}
/>
</div>
);
};
export default SearchBar;
The issue is that the library you are using does not allow for dynamic data. This is one of their earliest issues. They will not be working towards it. The whole library was built keeping static requirements in mind.
You have the option to use some other library MUI autocomplete. They support server calls.
You can also make your own component, if the requirement is not that complex.
Basically what you are supposed to have is a:
const debounceWait = 1000; //debounce time, you can keep it inside the component too
const AutoCompleteDropdown = () => {
const [query, setQuery] = useState("");
const [options, setOptions] = useState([]);
const ref = useRef(query); //ref to match query with input
const debounceRef = useRef(null); //ref to cancel existing timeouts for the fetch
const onSearch = (e) => {
let string = e.target.value;
setQuery(string);
};
useEffect(() => {
ref.current = query; //updating ref for the query
clearTimeout(debounceRef.current);
if (query.length < 2) {
setOptions([]);
} else {
debounceRef.current = setTimeout(() => {
clearTimeout(debounceRef.current);
fetch(`https://dummyjson.com/posts/search?q=${query}`)
.then((res) => res.json())
.then((json) => {
if (ref.current === query) //this check is necessary in case one server call started earlier finishes later then the other server call
setOptions(json.posts.map((x) => x.title));
})
.catch((error) => console.error(error));
}, debounceWait);
}
}, [query]);
console.log({ options });
return (
<div className="dropdown">
<div className="control">
<div className="selected-value">
<input
type="text"
value={query}
name="searchTerm"
onChange={onSearch}
/>
</div>
</div>
<div>
{options.map((option, index) => {
return <div>{option}</div>;
})}
</div>
</div>
);
};
Here is a demo
The above example should work for your use case. There is an inefficiency that API calls are not being cancelled. But that is not a direct requirement you have mentioned so I have not worked towards it.