I'm using React 16.13.0 and lodash. I was recommended to use debounce as a way to properly send search requests to my server as a user types in their search term. I implemented this ...
...
const handleChange = (event, searchTerm, setSearchTerm, setSearchResults) => {
console.log("search term:" + searchTerm);
const query = event.target.value;
setSearchTerm(query);
if (!query) {
setSearchResults( [] );
} else {
doSearch(query, searchTerm, setSearchResults);
}
}
const getDebouncedHandler = (e, handler, delay) => {
console.log("value:" + e.target.value);
_.debounce(handler, delay);
}
...
const Search = (props) => {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
const renderSearchResults = ...
return (
<div className="searchForm">
<input
type="text"
placeholder="Search"
value={searchTerm}
onChange={(e) => {getDebouncedHandler(e, (e) => {handleChange(e, searchTerm, setSearchTerm, setSearchResults); }, 100)}}
/>
{renderSearchResults()}
</div>
);
}
export default Search;
The problem is, although I see my "getDebouncedHandler" method getting called, I don't think
_.debounce(handler, delay);
is doing anything because I never see the method listed there invoked. What else do I need to do to get debounce handler invoked properly?
Edit: Adding in the logic I'm using for "doSearch"
const doSearch = (query, searchTerm, setSearchResults) => {
console.log("before fetch, with query:" + query);
const searchUrl = "/coops/?contains=" + encodeURIComponent(query);
fetch(searchUrl, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
console.log("returning data for " + searchTerm + " query:" + query);
console.log(data);
if (query === searchTerm) {
console.log("setting search results for search term:" + searchTerm);
setSearchResults(data);
}
});
}
Here is one way to implement this :
onChange
handler, this will cause a bad UX because some characters will be lost.doSearch
in your case.useMemo
to keep the reference, otherwise it will be created every time, and it will not be very useful (it has an internal state to keep track of the last time it was called and other stuff ...)._.debounce
it returns a function.useEffect
to call your debounced function when the search query changes.The code :
// Your expensive function
const doSearch = (query, callback) => {
const searchUrl = "/coops/?contains=" + encodeURIComponent(query);
fetch(searchUrl, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
callback(data);
});
}
// The debounced version
const doSearchDebounced = _.debounce(doSearch, 100);
const Search = (props) => {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
// Keep track of the last request ID
const lastRequestId = useRef(null);
// This hook will run when searchTerm changes
useEffect(() => {
if (!searchTerm) {
setSearchResults([]);
return;
}
// Update the last request ID
const requestId = performance.now();
lastRequestId.current = requestId;
// Let the debounced function do it's thing
doSearchDebounced(searchTerm, (results) => {
// Only set the data if the current request is the last one
if (requestId === lastRequestId.current) {
setSearchResults(results);
}
});
}, [searchTerm]);
// No debounce here, just getting the user's input
const handleChange = (e) => {
setSearchTerm(e.target.value);
};
const renderSearchResults = ...
return (
<div className="searchForm">
<input
type="text"
placeholder="Search"
value={searchTerm}
onChange={handleChange}
/>
{renderSearchResults()}
</div>
);
}