6

(for instance) I wish to limit user selecting only 3 options in my Autocomplete component, and disable the options when the length of TAG Array reaches 3. The problem is there is no limitMaxNumberOfTags option in the api, and i cant get any way to access the Selected tags array whatsoever {except the limitTags, which only limits the visible tags} . something along the lines of this might help getOptionDisabled={(options, tags) => (tags.length > 3 ? true : false)} . Here is my autocomplete implementation so far

<Autocomplete
  multiple
  id="tags-outlined"
  options={students}
  getOptionLabel={(option) => option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
  defaultValue={[...added]}
  onChange={(e, newVal) => setAdded([...newVal])}
  renderOption={(option, state) => {
    return (
      <Chip
        icon={
          <FaceIcon /> /*<Avatar color="primary" variant='outlined' size="small" className={classes.small}></Avatar>*/
        }
        label={option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
        color="default"
        variant="outlined"
        {...state}
      />
    );
  }}
  renderTags={(options, getTagProps) =>
    options.map((option) => (
      <Chip
        icon={
          <FaceIcon /> /*<Avatar color="primary" variant='outlined' size="small" className={classes.small}></Avatar>*/
        }
        label={option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
        color="primary"
        variant="outlined"
        {...getTagProps({})}
      />
    ))
  }
  filterSelectedOptions
  filterOptions={(options, state) =>
    options.filter((option) => {
      for (let i = 0; i < added.length; i++) {
        if (added[i]._id === option._id) {
          return false;
        }
      }
      return true;
    })
  }
  // --->         getOptionDisabled={(options) => (tags.length > 3 ? true : false)}
  renderInput={(params) => (
    <TextField {...params} variant="outlined" color="primary" label="Select Students" placeholder="Participant" />
  )}
/>
Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
doctor_acid
  • 91
  • 1
  • 1
  • 6

7 Answers7

2

Ran into similar issue recently. This is what I ended up doing. Basically you have to set the disabled flag on the chip itself directly, so it disables the text input, but not the chip. So you can still delete each chip.

export const AutoCompleteWithLimit: React.FC<Props> = ({
  disabled = false,
  limit = 2,
}) => {
  const [disableInput, setDisableInput] = useState<boolean>(
    value.length >= limit
  );

  return (
    <Autocomplete
      // Set disabled based on input
      disabled={disabled || disableInput}
      multiple
      renderTags={(tagValue, getTagProps) =>
        tagValue.map((option, index) => (
          <Chip
            key={index}
            label={option.name}
            {...getTagProps({ index })}
            // Set disable explicitly after getTagProps
            disabled={disabled}
          />
        ))
      }
      onChange={(_event: any, newValue: any[]) => {
        // do something else
        // set the disable input
        setDisableInput(newValue.length >= limit);
      }}
    />
  );
};
justindra
  • 166
  • 1
  • 9
2

Autocomplete added getOptionDisabled prop that can be used to disable all non selected options after maximum number of options is selected.

const a = ({limit = 3})=> {
    const [limitReached, setLimitReached] = useState(false);
    const [values, setValues] = useState([]);
    const onSelect = useCallback((newValues) => {
        setValues(newValues);
        setLimitReached(newValues.length >= limit);
    }, [limit]);


  const checkDisable = useCallback(option => limitReached && !values.includes(option), [limitReached, values]);

  return <Autocomplete
                getOptionDisabled={checkDisable}
                multiple
                onChange={onSelect}
                options={options}
                value={values}
            />
}

yehudaGold
  • 29
  • 1
1

In my case, this worked fine.

onChange={(e, newVal) => {
    if (newVal > 3) {
      newVal.pop(); 
    }
    setAdded([...newVal]);
}

Though the documentation explains that the value parameter of onChange prop is the new value, the actual content seems the array of all selected options.

And the last element in the value array is the newly selected option by the current change event. Therefore the code snippet above eliminates the exceeding element against the limited number (3 as above).

The latter type (Array<T>) for value parameter in the documentation is the case.

woniok
  • 159
  • 4
  • 9
0

I kind of found one solution to deal with it on my own, after fiddling through documentation for some unrelated Problem. The solution includes

  1. Setting value = {[...myStateVariable]}
  2. Using the reason parameter from the onChange((event, newValue, reason)=>{....}) callback
            <Autocomplete
                multiple
                id="tags-outlined"
                options={students}
                getOptionLabel={(option) => option.personalInfo.firstName+' '+option.personalInfo.lastName}
                defaultValue={[...added]}
                value={[...added]}
                onChange={(e, newVal, reason)=>{

                    if(reason==="select-option"){
                        addChips(newVal)
                    } else if(reason==="remove-option"){
                        handleDelete(newVal)
                    }
                }}

The HandleDelete and addChips method are as follows.

    const [added, setAdded] = useState([...added2])

    const handleDelete = (students)=>{
        setAdded([...students])
    }

    const addChips = (students)=>{
        if(added.length>= maxParticipation){
            alert('Cannot add more participants')
        }else{
            setAdded([...students])
        }
    }

the 'newValue' is first intercepted by the 'onChange' callback where it's length is evaluated and if the length is greater than a limiting value, the value update is cancelled. (Notice, the onChange that causes abortion of the process is only the one where "reason" = 'select-option' ). P.S- (forgot to mention about the disable Options query) the "disable" attribute on Options could also be manipulated in the renderOptions to disable all the options after "newVal" reaches a given length. [ solution for that(disable options) given in detail by @justindra ]

doctor_acid
  • 91
  • 1
  • 1
  • 6
0

I used slice to limit the maximum limit on renderTags before rendering the chips

<Autocomplete
  renderTags={(options, getTagProps) =>
    options.slice(0, 5).map((option) => (
      <Chip
        icon={
          <FaceIcon /> /*<Avatar color="primary" variant='outlined' size="small" className={classes.small}></Avatar>*/
        }
        label={option.personalInfo.firstName + ' ' + option.personalInfo.lastName}
        color="primary"
        variant="outlined"
        {...getTagProps({})}
      />
    ))
  }
/>
Hisham Haniffa
  • 449
  • 5
  • 7
0
 <Autocomplete
                  multiple
                  id="role"
                  options={rootStore.roleStore.listRole}
                  disableCloseOnSelect
                  value={rootStore.userStore.user.role}
                  onChange={(event, newValue) => {
                    setErrors({
                      ...errors,
                      role: Utils.validate.single(
                        newValue,
                        Utils.constraintsUser.role
                      ),
                    })
                    if (newValue.length > 2) {
                      alert("Please select max 2 labs")
                    } else {
                      rootStore.userStore.updateUser({
                        ...rootStore.userStore.user,
                        role: newValue,
                      })
                    }
                  }}
                  getOptionLabel={(option) => option.description || ""}
                  renderOption={(option, { selected }) => (
                    <React.Fragment>
                      <Checkbox style={{ marginRight: 8 }} checked={selected} />
                      {option.description}
                    </React.Fragment>
                  )}
                  renderInput={(params) => (
                    <TextField
                      {...params}
                      variant="outlined"
                      label="Labs"
                      placeholder="Labs"
                    />
                  )}
                />

perfect solution..

appasaheb4
  • 43
  • 5
  • Some explanation about the code that you changed/added or links to related documentation for insights and learning is often helpful for the author. – ranka47 Feb 11 '21 at 14:33
0

My solution,I think it's the most properly way:

<Autocomplete
  value={selectedTags}
  onChange={(ev, value: Tag[]) => {
    if (value.length <= 3) {
      setSelectedTags(value);
    } else {
      return;
    }
  }}
/>;
Li Mi
  • 1