mcclosa
mcclosa

Reputation: 1455

Trigger loadOptions in react-select/async once option has been click

I have an async react-select component

import React, { useRef, useState } from "react";
import Async from "react-select/async";

const MySelect = ({
  label,
  options,
  asyncSelect,
  loadOptions,
  components,
  placeholder,
  fixedHeight,
  onSelect,
  ...props
}) => {
  const ref = useRef();
  const asyncRef = useRef();

  const [fieldValue, setFieldValue] = useState();

  // Gets called when an option is chosen or created.
  const onChange = value => {
    setFieldValue(value);
    if (onSelect) onSelect(value);
  };

  // Toggle menu by presence of input.
  // Reset value if input is emptied.
  const onInputChange = (value, context) => {
    if (!value && context.action === "input-change") {
      onChange(null);
    }
  };

  // Restore input value.
  const onFocus = () => {
    if (fieldValue) {
      asyncRef.current.state.inputValue = fieldValue.label;
      asyncRef.current.select.state.inputValue = fieldValue.label;
      asyncRef.current.handleInputChange(fieldValue.label);
    }
  };

  // Close menu when pressing enter. Submit does not work on mobile.
  const onKeyDown = event => {
    if (event.keyCode === 13) {
      onMenuClose();
    }
  };

  // Blur select element to trigger onFocus on next click.
  const onMenuClose = () => {
    asyncRef.current.select.blur();
  };

  return (
    <>
      <div ref={ref} />
      <Async
        ref={asyncRef}
        {...props}
        value={fieldValue}
        components={components ? components : null}
        loadOptions={loadOptions ? loadOptions : () => null}
        placeholder={placeholder ? placeholder : "Select..."}
        searchPromptText="Start Typing"
        autoload
        isSearchable
        searchable
        cacheOptions
        // Hook into events to make the editing work.
        onChange={onChange}
        onInputChange={onInputChange}
        onFocus={onFocus}
        onKeyDown={onKeyDown}
        onMenuClose={onMenuClose}
      />
    </>
  );
};

export default MySelect;

I use this component in another component to load an array and filter options from the input value

const loadOptions = async inputValue => {
  return new Promise(resolve => resolve(getOptions(inputValue)));
};

const getOptions = async inputValue => {
  // how to trigger this to be called within loadOptions when option is selected?
  if (asyncOptions && asyncOptions.length > 0) {
    const options = asyncOptions.filter(item =>
      item.label.toLowerCase().startsWith(inputValue.trim().toLowerCase())
    );
    if (options && options.length > 0) {
      return options;
    }
    return [];
  }
  await delayWithPromise(1000);
  return [];
};

return (
  <div className="App">
    <MySelect
      name="addressLookup"
      className="addressLookupContainer"
      label="Address Lookup"
      asyncSelect
      components={{
        DropdownIndicator: () => null,
        IndicatorSeparator: () => null,
        NoOptionsMessage: val => <NoOptionsMsg>No Options</NoOptionsMsg>
      }}
      loadOptions={loadOptions}
      onSelect={val => console.log(`${val.value} selected`)}
    />
  </div>
);

However, I want it so when I select an option, it then loads another list (without the list closing, instead loads a new list in the old lists place), but I am unsure how I can retrigger the loadOptions prop within react-select/async when I click on an option?

An example would be an address search, where you search for a street/ post/zip code and there is an option stating how many address with that street, then selecting that option would then trigger loadOptions make an API call to find all address in that street, therefore, brining in a new list of options. Hopefully, that makes sense?

Any help would be greatly appreciated.

I have a CodeSandbox with an example.

Upvotes: 0

Views: 6148

Answers (1)

himayan
himayan

Reputation: 842

I had a go at the Codesandbox link you posted. The onSelect prop can be used to detect if user has selected anything.

  const [firstValue, setFirstValue] = useState("");
  const [secondValue, setSecondValue] = useState("");

  const onSelect = option => {
    if (firstValue) setSecondValue(option?.value);
    else setFirstValue(option?.value);
  };

Now we know, if the firstValue is selected, we would need to toggle the options. So, by that logic -

    const getOptions = async inputValue => {
    const optionChunk = firstValue ? secondOptions : firstOptions;
    if (optionChunk && optionChunk.length > 0) {
      const options = optionChunk.filter(item =>
        item.label.toLowerCase().startsWith(inputValue.trim().toLowerCase())
      );
      if (options && options.length > 0) {
        return options;
      }
      return [];
    }
    await delayWithPromise(1000);
    return [];
  };

Check out the codesandbox link for more details. I have tweaked a few variable names here and there, which made more sense to me.

EDIT

Re-opening the menu when the user selects the first value -

So, when we have firstValue and not the secondValue, we can pass a prop(let's say - retainFocus) -

<MySelect
   ... otherProps
   retainFocus={!!firstValue && !secondValue}
/>

In MySelect, we can add a useEffect on retainFocus, which would just re-focus the select box if passed as true.

  useEffect(() => {
    if (retainFocus) {
      asyncRef.current.select.focus();
    }
  }, [retainFocus]);

Check the updated codesandbox link.

EDIT

I also added a method to go back to first list when user input is empty.

Upvotes: 2

Related Questions