Reputation: 283
I created a custom autocomplete dropdown list, but I need to force the user to select something from the list, keeping at the same time the possibility of typing something in the input field.
How can I create such a validation? Autocomplete component is a list with the possibility to select a thing from the API, but the user still can type anything in the input field and send it as well. I want the user to be able submit the form ONLY with the suggested item. Could you please help me with that one?
Basically the question is: how can I check if the input value is the same as the item taken from the autocomplete component?
export type AutocompletePropsType = {
inputValue: string;
currentIndex: number;
onSelect: (selectedItem: string, slug?: string | null) => void;
hasError: boolean;
list: AutocompleteResponse['data'];
};
type PropsType = {
onChange: (value: string, type: string, isValid?: boolean, slug?: string) => void;
inputName: string;
type: InputType;
label: string;
autocompleteComponent: React.ComponentType<AutocompletePropsType>;
validator?: UseFieldValidatorType;
};
const SearchInput = (props: PropsType): JSX.Element => {
const { onChange, inputName, type, label, autocompleteComponent: AutocompleteComponent, validator } = props;
const autocompleteRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const classes = useStyles();
const router = useRouter();
const dispatch = useDispatch();
const [currentIdx, setCurrentIdx] = useState(-1);
const [state, { handleInputBlur, handleInputChange, handleInputFocus }] = useField({
inputValidator: validator,
});
const { isFocused, isValid, value } = state;
const [list, { hasError }] = useAutocomplete<AutocompleteResponse['data']>(value, async (query: string) =>
searchApi.autocomplete[type](query).then((res) => (res.data as unknown) as AutocompleteResponse['data']),
);
const handleSelect = (alias: string): void => {
handleInputChange(alias);
onChange(alias, type);
handleInputBlur();
};
const handleFocus = (): void => {
handleInputFocus();
inputRef.current.focus();
};
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
handleInputChange(event);
const isValueValid = validator ? validator(event.target.value).success : true;
onChange(event.target.value, event.target.name, isValueValid);
};
useClickOutside(autocompleteRef, () => {
handleInputBlur();
});
useEffect(() => {
(async (): Promise<void> => {
if (router.pathname !== '/' && !value) {
const { alias, slug } = await getInitialInputValuesFromQuery(router, type);
handleInputChange(alias);
onChange(alias, type, isValid, slug);
}
})();
}, []);
return (
<div className={classes['search-input']} ref={autocompleteRef}>
<div onClick={handleFocus}>
<label
className={cx(classes['search-input__input__label'], {
[classes['search-input__input__label--active']]: isFocused || !!value,
})}
>
{label}
</label>
<input
id={inputName}
onChange={handleChange}
onFocus={handleInputFocus}
name={inputName}
value={value}
className={classes['search-input__input__field']}
autoComplete="off"
checked={false}
ref={inputRef}
maxLength={25}
/>
<div
className={cx(classes['input__decorator'], {
[classes['input__decorator--focused']]: isFocused,
})}
/>
</div>
{transitions(
({ transform, opacity }, item) =>
item && (
<animated.div className={classes['search-input__dropdown']} style={{ opacity: opacity as any, transform }}>
{value.length > 2 && (
<AutocompleteComponent
onSelect={handleSelect}
inputValue={value}
currentIndex={currentIdx}
hasError={hasError}
list={list}
/>
)}
</animated.div>
),
)}
</div>
);
};
export default SearchInput;
Upvotes: 0
Views: 838
Reputation: 1006
Disclaimer: i don't fully read the code because there's a few things in there that I don't understand yet (typescript). But considering this:
Autocomplete component is a list
how can I check if the input value is the same as the item taken from the autocomplete
You can use Array.prototype.includes()
to match the user input with your list before sending the form.
let userInput = "foo"
let userInput2 = "opsIAmInvalid"
let apiList = ["foo", "doo", "boo"]
apiList.includes(userInput) // true
apiList.includes(userInput2) // false
Another broad approach could be done with a more complex component. Two ideas:
Upvotes: 1