JedWatson / JedWatson/react-select

AsyncCreatableSelect not allowing to enter more than character to search or create

Open
#5,922 0 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

this is my code below mentioned.AsyncCreatableSelect not allowing to enter more than character to search or create please suggest

import React, { useState, useEffect, useCallback } from 'react';
import Select, { StylesConfig, ActionMeta, components } from 'react-select';
import AsyncSelect from 'react-select/async';
import makeAnimated from 'react-select/animated';
import CreatableSelect from 'react-select/creatable';
import AsyncCreatableSelect from 'react-select/async-creatable';
import ReactTooltip, { Tooltip } from "react-tooltip";
import { DropdownProps, Option } from '@/Interface/Dropdown';
import dropdownData from '@/Mockup/Dropdown.json';
import useFetch from '@/Hooks/useFetch';
import { encrypt, decrypt } from '@/Utils/CryptoUtils';

const { alignmentClassMap }: { alignmentClassMap: Record<string, string> } = dropdownData;

const getSelectContainer = (options: { tooltipText: string; tooltipId: string }) => (props: any) => (
<components.SelectContainer
{...props}
innerProps={{
...props.innerProps,
'data-tip': options.tooltipText,
'data-for': options.tooltipId,
}}
/>
);

const Dropdown: React.FC = ({
Label,
Name,
Class,
Color = '#000000',
Placeholder,
Disabled,
ReadOnly = false,
Width = 'w-full',
Alignment = 'left',
Required = false,
Options,
Value: initialValue = null,
Reset = false,
IsSearchable = false,
Rtl = false,
LabelClass,
LabelColor = "#000000",
Icon,
IsMultiSelect = false,
EnableTooltip = true,
TooltipText = "dsagfsdg",
ValidationMessage,
onChange,
OptionsUrl = null,
Async = false,
AsyncOptionsUrl = null,
Algorithm = 'AES',
Encryption = true,
Decryption = true,
BlockHelper,
CreateNew = false,

}) => {

const [selectedValue, setSelectedValue] = useState<Option | Option[] | null>(initialValue);
const [optionsToUse, setOptionsToUse] = useState<Option[]>(Options);
const { data, isLoading, error } = useFetch<Option[]>(OptionsUrl || '');
const [inputValue, setInputValue] = useState('');
const [inputValueState, setInputValueState] = useState('');

useEffect(() => {
if (OptionsUrl) {
if (data) {
setOptionsToUse(data);
}
} else {
setOptionsToUse(Options);
}
}, [Options, OptionsUrl, data]);

// load options using API call
const loadOptionsPromise = async (inputValue: string): Promise<Option[]> => {
try {
const response = await fetch(${AsyncOptionsUrl}?search=${inputValue});
const json = await response.json();
return json;
} catch (error) {
console.error('Error loading options:', error);
return [];
}
};

const handleOnInputChange = (inputValue: string) => {
let result = inputValue.substring(0,3);
const forbiddenCharacters = /[^a-zA-Z0-9.-_ ]/g;
if(result.match(forbiddenCharacters)){
result = result.replace(forbiddenCharacters, '');
}
setInputValueState(result);
}

const createOption = (label: string) => ({
label,
value: label.toLowerCase().replace(/\W/g, ''),
});

const handleCreate = (inputValue: string) => {
const newOption = createOption(inputValue);
setOptionsToUse((prev) => {
const updatedOptions = [...prev, newOption];
console.log('Updated optionsToUse:', updatedOptions);
return updatedOptions;
});
console.info('optionstoUse',optionsToUse)
if (IsMultiSelect && Array.isArray(selectedValue)) {
const newSelectedValues = [...selectedValue, newOption];
setSelectedValue(newSelectedValues);
} else {
setSelectedValue(newOption);
}

setInputValue('');

};

const customStyles: StylesConfig<Option, boolean> = {
control: (provided, state) => ({
...provided,
width: '100%',
color: Color,
direction: Rtl ? 'rtl' : 'ltr',
borderColor: state.isFocused
? (Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)))
? 'red' : provided.borderColor
: provided.borderColor,
'&:hover': {
borderColor: state.isFocused
? (Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)))
? 'red' : provided.borderColor
: provided.borderColor,
},
}),
placeholder: (provided) => ({
...provided,
color: Color,
}),
singleValue: (provided) => ({
...provided,
color: Color,
}),
multiValue: (provided) => ({
...provided,
}),
multiValueLabel: (provided) => ({
...provided,
color: Color,
}),
multiValueRemove: (provided) => ({
...provided,
color: Color,
}),
input: (provided) => ({
...provided,
color: Color,
}),
};

const animatedComponents = makeAnimated();

// Handle change in selected value(s)
const handleChange = (option: any) => {
setSelectedValue(option);

if (onChange) {
  onChange(Name, option);
}

};

const handleInputChange = (newValue: string) => {
setInputValue(newValue);
return newValue;
};

useEffect(() => {
if (Encryption && selectedValue) {
const encryptedValue = Array.isArray(selectedValue)
? selectedValue.map(val => encrypt(val.value, Algorithm)).join(',')
: encrypt(selectedValue.value, Algorithm);
const selectElement = document.querySelector([name="${Name}"]);
if (selectElement) {
selectElement.setAttribute('data-encrypted-value', encryptedValue);
}
}
}, [selectedValue, Encryption, Algorithm, Name]);

if (isLoading) return

Loading...

;
if (error) return Error: {error.message};

return (
<div className={relative }>
{/* Label */}
{Label && (
<label className={${LabelClass}} style={{ color: LabelColor }}>
{Label}

)}

  {/* Dropdown */}
  <div className={`relative flex items-center ${!Rtl ? alignmentClassMap[Alignment] : ''}`}>
    {Async ?
          CreateNew ? // Async - true, create New Option - true
            <AsyncCreatableSelect
              name={Name}
              classNamePrefix="react-select"
              placeholder={Placeholder}
              isDisabled={Disabled || ReadOnly}
              isMulti={IsMultiSelect}
              isClearable={Reset}
              loadOptions={loadOptionsPromise}
              defaultOptions={optionsToUse}
              options={optionsToUse}
              defaultValue={initialValue}
              value={selectedValue}
              onChange={(option: any) => handleChange(option)}
              allowCreateWhileLoading={true}
              isSearchable={IsSearchable}
              onInputChange={handleInputChange}
              inputValue={inputValue}
              noOptionsMessage={() => (isLoading ? 'Loading...' : 'No options')}
              styles={customStyles}
              components={{
                SelectContainer: getSelectContainer({
                  tooltipText: TooltipText,
                  tooltipId: 'currency-tooltip',
                }),
              }}
              data-tooltip-id={EnableTooltip ? "currency-tooltip" : undefined}
              data-tooltip-content={EnableTooltip ? TooltipText : ValidationMessage}
              className={`${Class} ${Width}  border ${Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)) ? 'border-red-500' : 'border-gray-300'}`}
              onCreateOption={handleCreate}
            />
            : 
            <AsyncSelect // Async - true, create New Option - false
              name={Name}
              classNamePrefix="react-select"
              placeholder={Placeholder}
              isDisabled={Disabled || ReadOnly}
              isMulti={IsMultiSelect}
              isClearable={Reset}
              loadOptions={loadOptionsPromise}
              defaultOptions={optionsToUse}
              options={optionsToUse}
              defaultValue={initialValue}
              value={selectedValue}
              onChange={(option) => handleChange(option)}
              isSearchable={IsSearchable}
              onInputChange={handleInputChange}
              inputValue={inputValue}
              noOptionsMessage={() => (isLoading ? 'Loading...' : 'No options')}
              styles={customStyles}
              components={{
                SelectContainer: getSelectContainer({
                  tooltipText: TooltipText,
                  tooltipId: 'currency-tooltip',
                }),
              }}
              data-tooltip-id={EnableTooltip ? "currency-tooltip" : undefined}
              data-tooltip-content={EnableTooltip ? TooltipText : ValidationMessage}
              className={`${Class} ${Width}  border ${Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)) ? 'border-red-500' : 'border-gray-300'}`}
            />
      :  //Normal Select Async False
        CreateNew ?  //Async false create New

        <CreatableSelect
          name={Name}
          classNamePrefix="react-select"
          placeholder={Placeholder}
          isDisabled={Disabled || ReadOnly}
          isMulti={IsMultiSelect}
          isClearable={Reset}
          options={optionsToUse}
          defaultValue={initialValue}
          value={selectedValue}
          onChange={(option) => handleChange(option)}
          isSearchable={IsSearchable}
          styles={customStyles}
          components={{
            SelectContainer: getSelectContainer({
              tooltipText: TooltipText,
              tooltipId: 'currency-tooltip',
            }),
          }}
          data-tooltip-id={EnableTooltip ? "currency-tooltip" : undefined}
          data-tooltip-content={EnableTooltip ? TooltipText : ValidationMessage}
          className={`${Class} ${Width}  border ${Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)) ? 'border-red-500' : 'border-gray-300'}`}
          onCreateOption={handleCreate}
        />

        :  //Async false create New false
        <Select
          name={Name}
          classNamePrefix="react-select"
          placeholder={Placeholder}
          isDisabled={Disabled || ReadOnly}
          isMulti={IsMultiSelect}
          isClearable={Reset}
          options={optionsToUse}
          defaultValue={initialValue}
          value={selectedValue}
          onChange={(option) => handleChange(option)}
          isSearchable={IsSearchable}
          styles={customStyles}
          components={{
            SelectContainer: getSelectContainer({
              tooltipText: TooltipText,
              tooltipId: 'currency-tooltip',
            }),
          }}
          data-tooltip-id={EnableTooltip ? "currency-tooltip" : undefined}
          data-tooltip-content={EnableTooltip ? TooltipText : ValidationMessage}
          className={`${Class} ${Width}  border ${Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)) ? 'border-red-500' : 'border-gray-300'}`}
        />
    }
  </div>

  {/* Tooltip */}
  {EnableTooltip && (
    <Tooltip variant="info" place="bottom" id='currency-tooltip' />
  )}

   {/* Block Helper */}
  {BlockHelper  && (
    <p className="mt-2 text-sm text-gray-500">  {(BlockHelper || '')} </p>
  )}

  {/* Validation Message */}
  {Required && (!selectedValue || (Array.isArray(selectedValue) && selectedValue.length === 0)) && ValidationMessage && (
    <span className="validation-message">
      {ValidationMessage}
    </span>
  )}
</div>

);
};

export default Dropdown;

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 reported behavior with the shown AsyncCreatableSelect configuration, especially the handleInputChange path and its inputValue state. Compare the entered value with the three-character substring used there; done when the intended search or creation input is accepted beyond one character and the behavior is demonstrated consistently.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.