I would like to setup a component react-select to work server-side data and do server-side filtering, but it doesn't work for a plethora of reasons.
Can you explain it and also show working code?
Let's start by me expressing the opinion that react-select seems great, but not very clearly documented. Personally I didn't fall in love with the documentation for the following reasons:
CTRL+F on something everything lights up. Pretty uselessAnd so I will try to help a bit with this article, by giving steps by steps, code and problems + solutions.
const [options, setOptions] = useState([
    { id: 'b72a1060-a472-4355-87d4-4c82a257b8b8', name: 'illy' },
    { id: 'c166c9c8-a245-48f8-abf0-0fa8e8b934d2', name: 'Whiskas' },
    { id: 'cb612d76-a59e-4fba-8085-c9682ba2818c', name: 'KitKat' },
  ]);
 <Select
        defaultValue={options[0]}
        isClearable
        options={options}
        getOptionLabel={(option) => option.name}
        getOptionValue={(option) => option.id}
      />
It generally works, but you will notice that if I type the letter d which doesn't match any of the choices anywhere, choices stay, instead of showing "no options" as it should.

I will ignore this issue, since it is minor and seems unfixable.
So far so good, we can live with that small issue.
Our goal is now to simply swap the static data with server loaded data. Meh, how difficult could it be?
We will first need to swap <Select/> for <AsyncSelect/>. Now how do we load data?
So looking at the documentation there are multiple ways of loading data:
defaultOptions: The default set of options to show before the user starts searching. When set to true, the results for loadOptions('') will be autoloaded.
and
loadOptions: Function that returns a promise, which is the set of options to be used once the promise resolves.
Reading it carefully you understand defaultOptions needs to be a boolean value true and loadOptions should have a function returning the choices:
      <AsyncSelect
        defaultValue={options[0]}
        isClearable
        getOptionLabel={(option) => option.name}
        getOptionValue={(option) => option.id}
        defaultOptions
        loadOptions={loadData}
      />
Looks great, we have remote data loaded. But we want to preset our default value now. We have to match it by Id, rather than choosing the first one. Here comes our first problem:
PROBLEM: You can't set the
defaultValuein the very beginning, because you have no data to match it against. And if you try to set thedefaultValueafter component has loaded, then it doesn't work.
To solve that, we need to load data in advance, match the initial value we have, and once we have both of those, we can initialize the component. A bit ugly but that's the only way I could figure it out given the limitations:
const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);
const getInitial = async () => {
    // make your request, once you receive data:
    // Set initial object
    const init= res.data.find((item)=>item.id=ourInitialId);
    setInitialObject(init); 
    // Set data so component initializes
     setData(res.data);
  };
useEffect(() => {
    getInitial();
  }, []);
return (
     <>
      {data!== null && initialObject !== null ? (
        <AsyncSelect
          isClearable
          getOptionLabel={(option) => option.name}
          getOptionValue={(option) => option.id}
          defaultValue={initialObject}
          defaultOptions={options}
          // loadOptions={loadData} // we don't need this anymore
        />
      ) : null}
     </>
    )
Since we are loading the data ourselves, we don't need loadOptions so we will take it out. So far so good.
So now we need a callback that we can use for getting data. Let's look back at the documentation:
onChange: (no description, from section "StateManager Props")
onInputChange: Same behaviour as for Select
So we listen to documentation and go back to "Select Props" section to find:
onInputChange: Handle change events on the input`
Insightful...NOT.
We see a function types definition that seems to have some clues:

I figured, that string must by my text/query. And apparently it drops in the type of change. Off we go --
const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);
const getInitial = async () => {
    // make your request, once you receive data:
    // Set initial object
    const init= res.data.find((item)=>item.id=ourInitialId);
    setInitialObject(init); 
    // Set data so component initializes
     setData(res.data);
  };
useEffect(() => {
    getInitial();
  }, []);
  const loadData = async (query) => {
   // fetch your data, using `query`
   return res.data;
  };
return (
     <>
      {data!== null && initialObject !== null ? (
        <AsyncSelect
          isClearable
          getOptionLabel={(option) => option.name}
          getOptionValue={(option) => option.id}
          defaultValue={initialObject}
          defaultOptions={options}
          onInputChange={loadData} // +
        />
      ) : null}
     </>
    )
Data gets fetched with the right query, but options don't update as per our server data results. We can't update the defaultOptions since it is only used during initialization, so the only way to go would be to bring back loadOptions. But once we do, we have 2 calls on every keystroke. Blak. By countless hours and miracle of painstaking experimentation, we now figure out that:
USEFUL REVELATION:
loadOptionsactually fires on inputChange, so we don't actually needonInputChange.
<AsyncSelect
  isClearable
  getOptionLabel={(option) => option.name}
  getOptionValue={(option) => option.id}
  defaultValue={initialObject}
  defaultOptions={options}
  // onInputChange={loadData} // remove that
  loadOptions={loadData} // add back that
/>
Things look good. Even our d search has automagically been fixed somehow:

formik or whatever form value you haveTo do that we need something that fires on select:
onChange: (no explanation or description)
Insightful...NOT. We have a pretty and colorful definition again to our rescue and we pick up some clues:

So we see the first param (which we don't know what it is can be object, array of array, null, or undefined. And then we have the types of actions. So with some guessing we figure out, it must be passing the selected object:
We will pass setFieldValue function as a prop to the component:
onChange={(selectedItem) => {
  setFieldValue(fieldName, selectedItem?.id); // fieldName is also passed as a prop
}}
NOTE: careful, if you clear the select it will pass
nullforselectedItemand your JS will explode for looking for.idof undefined. Either use optional chaining or as in my case set it conditionally to''(empty string so formik works).
And so we are all set with a fully functional reusable Autocomplete dropdown select server-fetching async filtering, clearable thingy.
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/async';
export default function AutocompleteComponent({
  fieldName,
  initialValue,
  setFieldValue,
  getOptionLabel,
  queryField,
}) {
  const [options, setOptions] = useState(null);
  const [initialObject, setInitialObject] = useState(null);
  // this function only finds the item from all the data that has the same id
  // that comes from the parent component (my case - formik initial)
  const findByValue = (fullData, specificValue) => {
    return fullData.find((e) => e.id === specificValue);
  };
  const loadData = async (query) => {
    // load your data using query HERE
    return res.data;
  };
  const getInitial = async () => {
     // load your data using query HERE
      const fetchedData = res.data;
      // match by id your initial value
      const initialItem = findByValue(fetchedData, initialValue); 
      // Set both initialItem and data options so component is initialized
      setInitialObject(initialItem);
      setOptions(fetchedData);
    }
  };
  // Hit this once in the beginning
  useEffect(() => {
    getInitial();
  }, []);
  return (
    <>
      {options !== null && initialObject !== null ? (
        <AsyncSelect
          isClearable
          getOptionLabel={getOptionLabel}
          getOptionValue={(option) => option.id}
          defaultValue={initialObject}
          defaultOptions={options}
          loadOptions={loadData}
          onChange={(selectedItem) => {
            const val = (selectedItem === null?'':selectedItem?.id);
            setFieldValue(fieldName, val)
          }}
        />
      ) : null}
    </>
  );
}
AutocompleteComponent.propTypes = {
  fieldName: PropTypes.string.isRequired,
  initialValue: PropTypes.string,
  setFieldValue: PropTypes.func.isRequired,
  getOptionLabel: PropTypes.func.isRequired,
  queryField: PropTypes.string.isRequired,
};
AutocompleteComponent.defaultProps = {
  initialValue: '',
};
I hope this saves you some time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With