reactjsreact-hooksreact-multiselect-checkboxes

How can I display maximum item selected once i have selected 3 items from dropdown


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>
  );
}

Solution

  • 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