Change header color 🎲 🎲

Jake Paris

in Maine, in 2024

Debounce an input using select hook in WordPress/Gutenberg

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"
  />
  ...
}