25

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;

emepyc
  • 949
  • 1
  • 10
  • 23
  • Does this answer your question? [React-Select Async loadOptions is not loading options properly](https://stackoverflow.com/questions/52984105/react-select-async-loadoptions-is-not-loading-options-properly) – DSLuminary Nov 12 '19 at 21:49

6 Answers6

27

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}
     />
   )
 } 
Avkash
  • 439
  • 4
  • 8
  • 2
    Had the same issue and this is the correct answer, and should be flagged as so! – Solly Apr 06 '22 at 10:42
  • 1
    This answer worked well for us. If you want to keep the functions inside the function component use import { useMemo } from 'react'; function SearchboxTest() { const loadSuggestions = useMemo( () => debounce(_loadSuggestions, 300) , []); ... https://dmitripavlutin.com/react-throttle-debounce/ – alfthan Jan 12 '23 at 13:30
6

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

Lokesh sp
  • 81
  • 2
  • 6
6

For all of you people still struggling with this and don't want to install lodash only for debounce function like I do, here's my solution

debounce function

export default function debounce(fn, delay = 250) {
    let timeout;

    return (...args) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn(...args);
        }, delay);
    };
}

debounce implementation

const loadOptionsDebounced = useCallback(
    debounce((inputValue: string, callback: (options: any) => void) => {
        fetchOptions(inputValue).then(options => callback(options))
    }, 500),
    []
);

fetchOptions() is my async fetch function that returns array of options

1

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);
  })
});
};
Krol
  • 37
  • 3
1

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 />
}
wilbo
  • 71
  • 1
  • 2
0

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