Debounce an input using select hook in WordPress/Gutenberg
Written on July 22, 2022
I was building a component for the block editor which accepts user input (i.e. a text search) and searches for matching posts. For various reasons I couldn’t use the <URLInput>
component and needed to roll something more custom.
I got stuck on this because when using the useSelect
hook triggered by changes from a text input, each keyup would fire a change to the useSelect and therefore typing a single word like something resulted in 9 api calls.
Debounce to the Rescue
Yes, except that how? I struggled with this for a while before coming up with the following solution which works excellently.
import { debounce } from 'lodash';
import { useSelect } from '@wordpress/data';
import { TextControl } from '@wordpress/components';
// debounced function needs to be defined outside of render function
const debouncedSetValue = debounce( (val,setVal) => {
setVal( val );
}, 500);
export default function edit (props) {
// this value immediately updates for purpose of keeping text input responsive
const [searchTerm, setSearchTerm] = useState("");
// this value only updates after debouncing
const [searchTermDebounceable, setSearchTermDebounceable] = useState("");
// the useSelect hook has the debounced value as it's dependency, so it only makes another call
// when the debounced value changes
var resultsList = useSelect( select => {
var args = {
per_page: 10,
};
if( searchTermDebounceable )
args.search = searchTermDebounceable;
return select('core').getEntityRecords('postType', 'page', args);
}, [ searchTermDebounceable ]);
// called by the textInput, here we immediately update the first value,
// and update debouncedly the other
const setEventSearch = (str) => {
setSearchTerm(str);
debouncedSetValue( str, setSearchTermDebounceable);
};
// the text control doesn't use the state function to update, but the intermediary so that both
// state values are updated
<TextControl
value={ searchTerm }
onChange={ setSearch }
label="Search"
/>
...
}