Reputation: 1455
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
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