Reputation: 626
I'm trying to use debounce in lodash to delay the onChange, see the code below.
import React, { useState, useEffect, useCallback } from "react";
import { TopBar } from "@shopify/polaris";
import { debounce } from "lodash";
function SearchBar() {
const [searchValue, setSearchValue] = useState("");
const handleSearchFieldChange = ((value:string) => {
setSearchValue(value);
});
const debounceLoadData = useCallback(debounce({searchValue} => fetchData, 1000), []);
useEffect(() => {
debounceLoadData();
console.log({searchValue})
}, [searchValue]);
function fetchData(value:string) {
console.log("searchValue " + value);
}
const searchFieldMarkup = (
<TopBar.SearchField
onChange={handleSearchFieldChange}
value={searchValue}
placeholder="Search Value"
/>
);
return <TopBar searchField={searchFieldMarkup} />;
}
In the beginning, I was tring to use searchValue
in fetchData function but seems because of scope, it failed to read it, it was always empty though the state had been updated.
As a result, I try to pass it in from the debounceLoadData
but I don't know how I can do that as what in useCallback is a function invocation. How can I pass searchValue
in fetchData
inside debounce
.
Upvotes: 1
Views: 1389
Reputation: 19947
I never like useCallback
, it's quite a confusing hook, and I always use useMemo
instead since it totally covers what useCallback
can do (but not the other way around).
function SearchBar() {
const [searchValue, setSearchValue] = useState("");
const handleSearchFieldChange = ((value:string) => {
setSearchValue(value);
});
const debounceLoadData = useMemo(() => debounce(fetchData, 1000), []);
/**
* the equivalent of useCallback should be:
*
* const debounceLoadData = useCallback(debounce(fetchData, 1000), []);
*
* But I really advice against it!
* There's unnecessary function invocation compared to useMemo.
*/
useEffect(() => {
debounceLoadData(searchValue); // <- you should pass in arg
console.log({searchValue})
}, [searchValue]);
// ...
}
Yet for your case I don't think using lodash debounce is the best solution.
There's a hidden risk that the final invocation of effect fetchData
happens AFTER your component is unmounted. And if fetchData
contains some state mutation logic, that would raise an error of "Can't call setState (or forceUpdate) on an unmounted component." which is not destructive but not optimal either.
I suggest manually debounce call using setTimeout/clearTimeout
. It's pretty simple:
useEffect(() => {
const timeoutId = setTimeout(() => fetchData(searchValue), 1000)
return () => clearTimeout(timeoutId)
}, [searchValue])
Upvotes: 2
Reputation: 281646
lodash debounce takes in a function as the first argument. You can simply use fetchData
as the function and pass on the searchValue
to debounceLoadData
which will then be passed to fetchData
on invocation
const debounceLoadData = useCallback(debounce(fetchData, 1000), []);
useEffect(() => {
debounceLoadData(searchValue);
console.log({searchValue})
}, [searchValue]);
debounce actually returns a function, think of debounce as being implemented like
function debounce(func, wait) {
let timeout
return function(...args) {
const context = this
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(context, args), wait)
}
}
So basically debounceLoadData
is the returned function here and arguments passed to it i.e ...args
are being then passed to original function fetchData like func.apply(context, args)
Also debounceLoadData
is created only once as the callback dependency is []
, you whether you pass it to useEffect as a dependency of not will not make any difference.
Please read this post for missing dependency warning
How to fix missing dependency warning when using useEffect React Hook?
Upvotes: 4
Reputation: 6762
I think you are getting confused by the functional setState
syntax. Try this:
const debounceLoadData = useCallback(() => debounce(() => fetchData(searchValue), 1000), []);
Upvotes: 0