JedWatson / JedWatson/react-select

Passing a single string instead of the whole object

Open
#5,368 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

issue/bug-unconfirmed
Dominant language
TypeScript
Stars
28k
Forks
4.1k
PR merge metrics
No merged PRs in 30d

Description

Hello,

Initially it started as a question, however after having spent nearly 2 days googling and looking for a solution it gradually turned into an issue rather than a mere question. The problem arose when attempting to develop a Select to the following spec:

Using react-hook-form and react-select, create a select component that would allow searching live options from a REST API.

Below is the code to achieve the aforementioned task:

export default function GenericSelectWithApiSearch({
  url,
  fieldId,
  label,
  placeholder,
}: {
  url: string;
  queryParam: string;
  fieldId: string;
  label: string;
  placeholder: string;
}) {
  const [inputValue, setValue] = useState("");
  const [selectedValue, setSelectedValue] = useState(null);

  // handle input change event
  const handleInputChange = (value: string) => {
    setValue(value);
  };

  // load options using API call
  const loadOptions = async (inputValue: string) => {
    const queryUrl =
      inputValue.length > 0 ? url + "?search=" + inputValue : url;
    const abortController = new AbortController();
    const data = await getDataFromServer(queryUrl, abortController.signal);
    if (data.type === "success") {
      return data.value.docs;
    }
    return [];
  };

  const {
    control,
    formState: { errors },
  } = useFormContext(); // retrieve all hook methods
  return (
    <FormControl isInvalid={!!errors[fieldId]}>
      <FormLabel>{label}</FormLabel>

      <Controller
        control={control}
        name={fieldId}
        render={({ field: {onChange, ...rest} }) => (
          <AsyncSelect
            {...rest}
            cacheOptions
            defaultOptions
            getOptionLabel={(e) => e.name}
            getOptionValue={(e) => e._id}
            loadOptions={loadOptions}
            onInputChange={handleInputChange}
            placeholder={placeholder}
            onChange={onChange}
          />
        )}
        rules={{ required: true }}
      />
      <ErrorMessage errors={errors} name={fieldId} />
    </FormControl>
  );
}

In a nutshell, the problem is as follows:

A form validator is expecting a string to result from this field. This validation rule check will always fail, because react-select will always return a full object. Now, I don't know what getOptionValue is for then, I expect it does something, because getOptionLabel is performing well in updating a changed label, however the former doesn't seem to be doing anything to cure the problem. You would intuitively expect that something called getOptionValue would be actually able to do something useful with the option value, say ultimately pass it to the form. I am assuming, both of these properties serve the task of updating the react-select component after the onChange event and that's it.

Tinkering with onChange, for example doing the following:

onChange={(e) => {
  onChange(e._id)
}}

Will work in setting a correct single value, but will break both getOptionLabel and getOptionValue . I suspect it is because the latter 2 are still expecting the object to access, while onChange modifies the value into a string.

Which brings me to conclude that there is no way to pass just a single selected value, let's say a single string, into a form without breaking the whole select component.

Is this really the case? If yes, it seems very limiting from a design perspective. Or am I missing something and there is indeed a way?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the supplied GenericSelectWithApiSearch example, focusing on the AsyncSelect, Controller, getOptionLabel, getOptionValue, and onChange interaction. Check the documented behavior and existing examples for controlled values, then define whether the expected result is supported and what form value and displayed option should be preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.