'react-select debounced async call not displaying suggestions

I'm using react-select loading the results from an api and debouncing the queries with lodash.debounce:

import React, {useState} from 'react';
import AsyncSelect from 'react-select/lib/Async';
import debounce from 'lodash.debounce';
import {search} from './api';

const _loadSuggestions = (query, callback) => {
  return search(query)
    .then(resp => callback(resp));
};

const loadSuggestions = debounce(_loadSuggestions, 300);

function SearchboxTest() {
  const [inputValue, setInputValue] = useState("");

  const onChange = value => {
    setInputValue(value);
  };

  return (
    <AsyncSelect
      value={inputValue}
      loadOptions={loadSuggestions}
      placeholder="text"
      onChange={onChange}
    />
  )
}

It seems to work fine when I enter something in the searchbox for the first time (query is debounced and suggestions populated correctly), but if I try to enter a new value, a second fetch query is fired as expected, but the suggestions coming from that second call are not displayed (and I don't get the "Loading..." message that react-select displays).

When I don't debounce the call the problem seems to go away (for the first and any subsequent calls):

import React, {useState} from 'react';
import AsyncSelect from 'react-select/lib/Async';
import {search} from '../../api';

const loadSuggestions = (query, callback) => {
  return search(query)
    .then(resp => callback(resp));
};

function SearchboxTest() {
  const [inputValue, setInputValue] = useState("");

  const onChange = value => {
    setInputValue(value);
  };

  return (
    <AsyncSelect
      value={inputValue}
      loadOptions={loadSuggestions}
      placeholder="text"
      onChange={onChange}
    />
  )
}

Any idea what is going on? Any help to understand this issue would be much appreciated.

M;



Solution 1:[1]

I was aslo facing the same issue and got this solution.

If you are using callback function, then You DON'T need to return the result from API.

Try removing return keyword from _loadSuggestions function. as shown below

 import React, {useState} from 'react';
 import AsyncSelect from 'react-select/lib/Async';
 import debounce from 'lodash.debounce';
 import {search} from './api';
 
 const _loadSuggestions = (query, callback) => {
   search(query)
     .then(resp => callback(resp));
 };
 
 const loadSuggestions = debounce(_loadSuggestions, 300);
 
 function SearchboxTest() {
   const [inputValue, setInputValue] = useState("");
 
   const onChange = value => {
     setInputValue(value);
   };
 
   return (
     <AsyncSelect
       value={inputValue}
       loadOptions={loadSuggestions}
       placeholder="text"
       onChange={onChange}
     />
   )
 } 

Solution 2:[2]

Use react-select-async-paginate package - This is a wrapper on top of react-select that supports pagination. Check it's NPM page

React-select-async-paginate works effectively with internal denounce. You can pass debounce interval in the props.

<AsyncPaginate
  value={value}
  loadOptions={loadOptions}
  debounceTimeout={300}
  onChange={setValue}
/>

Here is codesandbox example

Solution 3:[3]

Maybe this will help someone.

freeze = false //mark delay 
timer //saved timer

loadAddress = async (strSearch: string) => {
   this.freeze = true //set mark for stop calls
   return new Promise(async (res, err) => { //return promise 
   let p = new Promise((res, err) => {
     if(this.freeze) clearTimeout(this.timer) //remove  prev timer 
     this.timer = setTimeout(async () => {
        this.freeze = false
        const r = await this.load(strSearch)//request
        res(r);
     }, 2000)
  })

  p.then(function (x) {
     console.log('log-- ', x);
     res(x);
  })
});
};

Solution 4:[4]

What worked for me was, instead of making use of the default debounce from lodash, making use of the debounce-promise package:

import React from 'react';
import debounce from 'debounce-promise';
import AsyncSelect from 'react-select/async';

const debounceFunc = debounce(async () => {
    return [] // SelectOptions
}, 200);

export function MyComponent() {
  return <AsyncSelect loadOptions={debounceFunc} defaultOptions />
}

Solution 5:[5]

In my solution help me following. I have AsyncSelect where i want data from https://github.com/smeijer/leaflet-geosearch. My provider is:

const provider = new OpenStreetMapProvider({
    params: {
        countrycodes: "cz",
        limit: 5
    }
});

const provider you will see below one more time.

<AsyncSelect className="map-search__container"
                         classNamePrefix="map-search"
                         name="search"
                         cacheOptions
                         components={{DropdownIndicator, IndicatorSeparator, Control, NoOptionsMessage, LoadingMessage}}
                         getOptionLabel={getOptionLabel}
                         getOptionValue={getOptionValue}
                         loadOptions={getData}
                         onChange={handleChange}
                         isClearable
                         placeholder={inputSave || ""}
                         value=""
                         inputValue={input}
                         onMenuClose={handleMenuClose}
                         onInputChange={handleInputChange}
                         onFocus={handleFocus}
                         blurInputOnSelect
                         defaultOptions={true}
                         key={JSON.stringify(prevInputValue)}
            />

cachceOptions - is basic
components - my components
loadOptions - is the most important!
anything else is not important

const getData = (inputValue: string, callback: any) => {
        debouncedLoadOptions(inputValue, callback);
    }
const debouncedLoadOptions = useDebouncedCallback(fetchData, 750);
import {useDebouncedCallback} from 'use-debounce';

This use was my key for solution. I tried debounce from lodash, but i had still problem with not working debouncing. This use help me and did my problem solve.

const fetchData = (inputValue: string, callback: any) => {
        return new Promise((resolve: any) => {
            resolve(provider.search({query: inputValue || prevInputValue}));
        }).then((json) => {
            callback(json);
        });
    };

prevInputValue is props from parent... (you don't need it) Callbacks was second key. First was useDebouncedCallback.

#sorryForMyEnglish

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Avkash
Solution 2
Solution 3
Solution 4 wilbo
Solution 5 Ji?í Zeman