0

I have multiselect dropdown items with checkboxes in my react app, what I am trying to achieve is if I checked any three items, then dropdown should display maximum items selected and if I unchecked anyone of them, it should display back the drop down box with items. Somehow it doesn't work, could someone please advise.

CodeSanbox link

https://codesandbox.io/s/musing-sun-swvj6y?file=/src/App.js

import { useState } from "react";
import Multiselect from "multiselect-react-dropdown";
import "./styles.css";

export default function App() {
  const options = [
    { key: "Apple", email: "apple@test.com", id: 1 },
    { key: "Orange", email: "oranges@test.com", id: 2 },
    { key: "Mango", email: "mango@test.com", id: 3 },
    { key: "Grapes", email: "grapes@test.com", id: 4 }
  ];

  const [option, setOption] = useState([]);
  const [selectedOption, setSelectedOption] = useState([]);
  const [maxOptions, setMaxOptions] = useState(0);

  const handleTypeSelect = (e, i) => {
    const copy = [...selectedOption];
    copy.push(e[3 - maxOptions]);
    setSelectedOption(copy);
    setMaxOptions((prevState) => prevState - 1);
  };

  const handleTypeRemove = (e) => {
    const copy = [...selectedOption];
    let index = copy.indexOf(e);
    copy.splice(index, 1);
    setSelectedOption(copy);
    setMaxOptions((prevState) => prevState + 1);
  };

  options.forEach((option) => {
    option.displayValue = option.key + "\t" + option.email;
  });

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Multiselect
        onSelect={(e) => handleTypeSelect(e, selectedOption.length)}
        onRemove={handleTypeRemove}
        options={selectedOption.length + 1 === maxOptions ? [] : options}
        // options={!showOptions ? [] : option}
        displayValue="displayValue"
        showCheckbox={true}
        emptyRecordMsg={"Maximum fruits selected !"}
      />
    </div>
  );
}
soccerway
  • 10,371
  • 19
  • 67
  • 132
  • Didn't quite understand your question. Could you explain with an example? Say, if I select Apple, Orange, and Mango, what should be displayed? Should it disable all other options or should it remove all other options with some helper text saying "Maximum items selected"? – vighnesh153 Oct 27 '22 at 01:38
  • Ok will try to explain, as you have seen there are four items in the dropdown down right. So as a user if I select any 3 items from the drop down, it should hide or remove all of the remaining items from dropdown and display text "Maximum items selected". But the selected items should display the drop down search area. Once any of the selected items is removed by click on the close icon, it should display back the whole list – soccerway Oct 27 '22 at 01:50
  • I have added my codesandbox link to the question already – soccerway Oct 27 '22 at 01:50
  • Checked their documentation and I don't think they have a controlled way of handling selection/deselection.. The author has mentioned it here: https://github.com/srigar/multiselect-react-dropdown/issues/189 – vighnesh153 Oct 27 '22 at 01:51

2 Answers2

1

The library doesn't support manually selecting/deselecting the options. Reference

There is one hack that you can do. You can play around with key. Use the selectedItems as key and then it will re-mount the component whenever the selectedItems changes.

Note that this hack is not the recommended way to do "React".

You update the options based on the size of the selected options

const maxSelectableItems = 3;

const options = [
  { key: "Apple", email: "apple@test.com", id: 1 },
  { key: "Orange", email: "oranges@test.com", id: 2 },
  { key: "Mango", email: "mango@test.com", id: 3 },
  { key: "Grapes", email: "grapes@test.com", id: 4 }
];

<Multiselect
  // This will re-mount your component whenever 
  // selected items changes
  key={selectedItems} 

  onSelect={handleSelection}
  onRemove={handleRemove}

  // This will set the pre-selected values on re-mount
  selectedValues={selectedItems}

  options={
  selectedItems.length === maxSelectableItems 
    ? [] 
    : options.map((o) => ({ 
      ...o, 
      displayValue:  `${o.key}\t${o.email}`
    }))
  }
  displayValue="displayValue"
  showCheckbox
  emptyRecordMsg={"Maximum fruits selected !"}
/>

And, your handleSelection and handleRemove will look like this:

const [selectedItems, setSelectedItems] = useState([]);

const handleSelection = (selectedItems) => {
  setSelectedItems(selectedItems.slice(0, maxSelectableItems));
};

const handleRemove = (selectedItems) => {
  setSelectedItems(selectedItems);
};

One issue with this is that since it re-mounts the entire multi-select component, when you select/remove an item, the input will lose focus. So, you will have to manually give focus to the input after selection/removal.

const focusOnInput = () => {
  setTimeout(() => {
    // You can use a better selector (this is just a generic input selector)
    document.querySelector("input").focus();
  
  // Adding some delay to allow the component to re-mount
  }, 10);
};

And then, use this in your selection/removal handlers

const handleSelection = (selectedItems) => {
  setSelectedItems(selectedItems.slice(0, maxSelectableItems));
  focusOnInput()
};

const handleRemove = (selectedItems) => {
  setSelectedItems(selectedItems);
  focusOnInput()
};

Here is a link to a working sandbox

vighnesh153
  • 4,354
  • 2
  • 13
  • 27
  • ..Thank you, one question, may i know what is the purpose of useRef() here ? – soccerway Oct 27 '22 at 02:55
  • 1
    I was testing if we can focus the input using refs, but apparently, the ref isn't forwarded to the input. So, I had to fallback to using `document.querySelector`. you can remove the refs. – vighnesh153 Oct 27 '22 at 03:52
1
import { useState, useRef } from "react";
import Multiselect from "multiselect-react-dropdown";
import "./styles.css";

export default function App() {
  const options = [
    { key: "Apple", email: "apple@test.com", id: 1 },
    { key: "Orange", email: "oranges@test.com", id: 2 },
    { key: "Mango", email: "mango@test.com", id: 3 },
    { key: "Grapes", email: "grapes@test.com", id: 4 }
  ];

  const [option, setOption] = useState([]);
  const [selectedOption, setSelectedOption] = useState([]);

  const fruitmultiselect = useRef(null);

  options.forEach((option) => {
    option.displayValue = option.key + "\t" + option.email;
  });

  return (
    <div className="App">
      <h1>Hello CodeSandbox3</h1>
      <Multiselect 
        ref={fruitmultiselect}
        onSelect={(selectedList, selectedItem) => {
          if(selectedList.length === 3){
            fruitmultiselect.current.toggelOptionList()
            console.log("onSelect length: "+selectedList.length);
          }
        }}
        options={options}
        displayValue="displayValue"
        showCheckbox={true}
        selectionLimit={3}
        closeOnSelect={true}
        emptyRecordMsg={"Maximum fruits selected !"}
      />
    </div>
  );
}