tbleckert / react-select-search

⚡️ Lightweight select component for React
https://react-select-search.com
MIT License
676 stars 149 forks source link

Async results not selectable #150

Closed bealbrown closed 3 years ago

bealbrown commented 3 years ago

Hello,

Just wanted to mention that I had some difficulty implementing the async promise-based approach to using this library.

The issue I was having is that when a user selects an option that was populated by async API, it doesn't appear to properly select, and doesn't fire any events.

This behavior can be seen here: https://react-select-search.com/?path=/story/async--fetch

The way I ended up fixing this is by additionally writing the results for the API call to an object I passed to options, instead of passing an empty array as the example above models.

i.e.:


useEffect(() => {
  ApiClient.get("/search")
    .then((res) => setallStocks(formatStocks(res.data)))
    .catch(console.log);
}, []);

const matchStocks = function (searchTerm) {
    // console.log("searchTerm is ", searchTerm, typeof searchTerm);
    searchTerm = searchTerm.toUpperCase();
    var filteredArray = allStocks.filter(function (obj) {
      return obj.value.includes(searchTerm);
    });

    return filteredArray;
  };

const formatStocks = function (data) {
  console.log("unformatted data is ", data);

  let array = [];

  data.forEach(function (el) {
    array.push({
      name: el["name"],
      value: el["name"],
      exchange: el["exchange"]
    });
  });

  // console.log("allstocks is", array);
  console.log("formatted data is ", array);
  return array;
};

const handleSelect = function (selectedStock) {
  console.log("handling change", selectedStock);

  // unfortunately, search module just returns value, not obj, so we have to search again to find exchange

  var theStock = matchingStocks.filter(function (obj) {
    return obj.value == selectedStock;
  });

    console.log(exchangeCode);

    console.log(history);
    history.push({
      pathname: `/stock/${exchangeCode}:${theStock[0]["name"] + "/D"}`
    });
  }
};

const getOptions = function (query) {
  let results = matchStocks(query);
  setMatchingStocks(results);
  return results;
};

return (
  <div class="topBar">
    <SelectSearch
      options={matchingStocks}
      onChange={handleSelect}
      getOptions={(query) => {
        return new Promise((resolve, reject) => {
          resolve(getOptions(query));
        });
      }}
      search
      autoComplete="false"
      placeholder="Search..."
    />
  </div>
kjkurtz commented 3 years ago

I just experienced this as well. I think it is a regression in the newest release. I reverted back to 2.2.0 and things are all working again. In 2.2.3 the only way to select an async result is to use the keyboard, mouse events aren't getting handled properly from what I can tell.

bealbrown commented 3 years ago

So I tried reverting back to 2.2.0 and that didn't help.

I feel like this is super hacky, but the way I was able to get this working is by creating an empty promise that calls a function that uses the state hook to update the array which holds the options. Nothing else I tried worked -- it would either totally fail, or result in a race condition in which the results of the matching stocks query were always slower than the state hook updating, so the display would show the results of the previous query (i.e. show all matching for "ab" even though the event fired on the keypress of "c" for "abc)

Not sure if this is helpful since it feels hacky, but anyhow I'm rolling with it.

I should mention that this may be a particular case of using the async functionality to run a local search function, which executes quickly -- it's not polling an API, just sorting the data it already has.


const [matchingStocks, setMatchingStocks] = useState([]);

const getOptions = function (query) {
  let results = matchStocks(query);
  setMatchingStocks(results);
};

return (
<div className="topBar">
  <SelectSearch
    options={matchingStocks}
    onChange={handleSelect}
    getOptions={(query) => {
      console.log(query);
      return new Promise(() => {
        getOptions(query);
      });
    }}
    renderValue={(valueProps) => (
      <input
        className="select-search__input"
        {...valueProps}
        onKeyDown={(e) => handleInputKeyDown(e)}
      />
    )}
    search
    autoComplete="false"
    placeholder="Search..."
  />
</div>
);
bansalvks commented 3 years ago

https://user-images.githubusercontent.com/8565993/104350590-53ed9a80-552a-11eb-85c6-a1d0abb5dc1f.mov

bealbrown commented 3 years ago

That was the behavior I was experiencing. Still using my above solution, but would appreciate some clarity if possible. Thanks!

bansalvks commented 3 years ago

the drink is not selecting, @tbleckert this bug is not fixed