Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set value of AsyncSelect outside of the component? #3761

Closed
MarcosCunhaLima opened this issue Sep 17, 2019 · 13 comments
Closed

How to set value of AsyncSelect outside of the component? #3761

MarcosCunhaLima opened this issue Sep 17, 2019 · 13 comments
Labels
awaiting-author-response Issues or PRs waiting for more information from the author issue/reviewed Issue has recently been reviewed (mid-2020)

Comments

@MarcosCunhaLima
Copy link

I have read all the documentation of AsyncSelect and Select but I didn't read anything regarding this matter.
The problem arose after upgrading from V1 to V3. In V1, I could set value property (a simple value or string) and this would trigger the async loading procedure which would get the right value object (label and value).
But, in V3, you should set value with the whole structure, eg, {label: xxx, value: yyy} in order to show it correctly.

For instance, we have this sample:

import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
import { colourOptions } from '../data';

type State = {
  inputValue: string,
};

const filterColors = (inputValue: string) => {
  return colourOptions.filter(i =>
    i.label.toLowerCase().includes(inputValue.toLowerCase())
  );
};

const promiseOptions = inputValue =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve(filterColors(inputValue));
    }, 1000);
  });

export default class AsyncMulti extends Component<*, State> {
  state = { inputValue: '' };
  handleInputChange = (newValue: string) => {
    const inputValue = newValue.replace(/\W/g, '');
    this.setState({ inputValue });
    return inputValue;
  };
  render() {
    return (
      <AsyncSelect
        isMulti
        cacheOptions
        defaultOptions
        value = {1} // this won't work
        value = {{value: 1, label: "black"}}
        loadOptions={promiseOptions}
      />
    );
  }
}


All the logic of loading colors is inside the component. In order to set a value from outside, I would have to (in an external procedure) load the array and search for the value which, I think, would duplicate code unnecessarily.

Is there a way to set a value and this would trigger internal loading procedure in order to search for the correct pair?

@Xartok
Copy link

Xartok commented Oct 4, 2019

Using onInputChange prop with your handleInputChange method will trigger a new loadOptions call with the inputValue returned by handleInputChange.

<AsyncSelect
        isMulti
        cacheOptions
        defaultOptions
        value = {1} // this won't work
        value = {{value: 1, label: "black"}}
        loadOptions={promiseOptions}
        onInputChange={this.handleInputChange}
/>

@MarcosCunhaLima
Copy link
Author

Hi @Xartok

The problem is not when user is changing it (after the select has focus and user is typing something) as this is working.

The problem is when I set Value prop externally (first time, when, for instance, I set Value prop from database). This won't trigger onInputChange anymore and, in not doing so, it won't load the array options.
In V1, I could set Value prop with a simple string (the key) and it will trigger loadOptions promise and will try to get the correct value/label item.
In V3, you should set Value prop with value/label.

@Xartok
Copy link

Xartok commented Oct 8, 2019

So actually, when you set the value from your database, what is displayed:

  • in the menu when you open it
  • in the value container (where it should contain the value here with the label "black")

@MarcosCunhaLima
Copy link
Author

In the menu: nothing (as it expects a pair {value/label} in the Value prop.
In the Value container: the value I've set (for instance, in the example, "1")

@Xartok
Copy link

Xartok commented Oct 11, 2019

Ok, so do you store the label along with the value in the database ? If this is the case you can set the value of the AsyncSelect to the object {label: dbLabel, value: dbValue} (without doing loadOptions('').then(options => options.find(opt => opt.value === dbValue)) as this is supported (it will filter out the option with this value in the menu) and avoid an unnecessarily call to loadOptions. But it requires a modification of your database query...

@MarcosCunhaLima
Copy link
Author

Yes, this is what I've done. But I have to (as you said) change my query (outside of the component) and manually craft a label/value object. The problem with this approach is that I had to duplicate this query (one query to search for the label before creating the component) and the other inside the component to loadOptions.
Before this version, I could just set the value and it will trigger loadOptions and make this label/value automatically.

@bladey bladey added the issue/reviewed Issue has recently been reviewed (mid-2020) label Jun 18, 2020
@alexandrehpiva
Copy link

Yes, this is what I've done. But I have to (as you said) change my query (outside of the component) and manually craft a label/value object. The problem with this approach is that I had to duplicate this query (one query to search for the label before creating the component) and the other inside the component to loadOptions.
Before this version, I could just set the value and it will trigger loadOptions and make this label/value automatically.

Same here. I have only the value/id (not the entire object, like: { value: '1': label: 'First' }). So I set defaultValue like this: { value: form.fieldId } but I don't have the label (It will be searched by loadOptions).

So I need to load my option before render AsyncSelect and pass value or defaultValue prop

@ebonow
Copy link
Collaborator

ebonow commented Dec 18, 2020

Greetings,

Is this still an issue as it sounds like there is a solution?

This type of behavior would require a controlled input and updating the value once the defaultOptions load.

Here is a working demo: codesandbox

As a note, if you would like to trigger the onChange event, there is a selectOption available in the select ref which I believe triggers the onChange methods.

const AsyncExample = ({ defaultOptionValue, ...props }) => {
  const [value, setValue] = useState();
  const [defaultValue, setDefaultValue] = useState();

  useEffect(() => {
    setValue(defaultValue);
  }, [defaultValue]);

  const loadOptions = (searchKey) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        !defaultValue &&
          setDefaultValue(options.find((o) => o.value === defaultOptionValue));
        const filtered = options.filter((o) => o.label.includes(searchKey));

        resolve(filtered);
      }, 5000);
    });
  };

  const onChange = (option) => setValue(option);

  return (
      <Async
        loadOptions={loadOptions}
        defaultOptions
        value={value}
        onChange={onChange}
      />
   );
};

@ebonow ebonow added the awaiting-author-response Issues or PRs waiting for more information from the author label Dec 18, 2020
@ebonow
Copy link
Collaborator

ebonow commented Jan 8, 2021

In an effort to cleanup stale issues, I will be closing this. Please feel free to reply or create a new issue if there are any concerns or reproducible bugs.

@ebonow ebonow closed this as completed Jan 8, 2021
@flora8984461
Copy link

flora8984461 commented Feb 6, 2021

I am doing a similar thing as @MarcosCunhaLima described, I am prefilling the data from the database (which cannot have all those exactly the same data as {value: "", label: "" }), and actually, I was adding more other properties such as {value: "", label: "", thirdProperties... } which makes me hard to prefill the data if the object format is a must. We can do a filter in simple , as this question (https://stackoverflow.com/questions/52938331/how-can-i-work-with-just-values-in-react-select-onchange-event-and-value-prop) and https://github.com//issues/3063 shows, but it's hard for AsyncSelect 😥 My formatting is broken in all react-select issues.... Sorry about this

@ebonow
Copy link
Collaborator

ebonow commented Feb 6, 2021

Greetings @flora8984461 ,

This would require a controlled input so that a fetch can be made to get the default value.

const AsyncLookup = props => {
  const { defaultId, ...selectProps } =props;
  
  const [ value, setValue ] = useState();
  const [ isLoading, setIsLoading ] = useState(false);

  const onChange = opt => {
    setValue(opt);
    props.onChange && props.onChange(opt);
  }

  useEffect(() => {
     if (!defaultId) return;
     
    setIsLoading(true);
    fetch('your.api.com').then(resp => {
      const defaultValue = resp.results.find(x => x.id === defaultId);
      defaultValue && setValue(defaultValue);
    }).finally(() => setIsLoading(false));
    
  }, [ defaultId ]);
  
return <AsyncSelect {...selectProps} onChange={onChange} value={value} isLoading={isLoading} />
}

Let me know if this makes sense or needs a bit of explanation whats going on

@stefanoverna
Copy link

This is what works for me:

import AsyncSelect from 'react-select/async';
import { client } from 'api/dato';
import debounce from 'debounce-async';
import { useMemo, useState } from 'react';

const isOptionSelected = (option, values) => {
  return values.some(({ value }) => option.value === value);
};

export default function AccountInput({ field, form }) {
  const [lastOptions, setLastOptions] = useState([]);

  const loadOptions = useMemo(() => {
    return debounce(async (inputValue) => {
      const { data } = await client
        .url('/accounts')
        .query({ q: inputValue, id: field.value })
        .get()
        .json();

      const options = data.map((account) => ({
        value: account.id,
        label: `${account.attributes.email} (#${account.id})`,
      }));

      setLastOptions(options);

      return options;
    }, 300);
  }, [field.value, setLastOptions]);

  return (
    <div>
      <AsyncSelect
        cacheOptions
        loadOptions={loadOptions}
        defaultOptions
        isOptionSelected={isOptionSelected}
        value={lastOptions.find((option) => option.value === field.value)}
        onChange={(option) => {
          form.setFieldValue(field.name, option.value);
        }}
      />
    </div>
  );
}

@sparcbr
Copy link

sparcbr commented Aug 4, 2021

Thanks @stefanoverna, this part helped me:

value={lastOptions.find((option) => option.value === field.value)}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting-author-response Issues or PRs waiting for more information from the author issue/reviewed Issue has recently been reviewed (mid-2020)
Projects
None yet
Development

No branches or pull requests

8 participants