Remove options that are selected from all dropdowns

Viewed 1269

I have an array of dropdowns rendered by useFieldArray() from react-form-hook and mapped to react to jsx. The trick is to remove options from all dropdowns that are already selected.


So if there's 3 dropdowns that have array of options:

[ 'apple', 'banana', 'pineapple', 'pear', 'mango' ]

AND

1st dropdown: has apple selected

2nd: banana

3rd: pineapple

Dropdown options of all three would just show ['pear', 'mango' ] since the rest is selected.


What would be the best way to implement it in react/react with redux?

So that dropdown option list would dynamically change according to options selected

3 Answers

We can use a very simple procedure here with javascript filter() function

use a state for selected items like this

const [selectedItems, setSelectedItems] = useState([])
const fruits = [ 'apple', 'banana', 'pineapple', 'pear', 'mango' ]

Then use filter before mapping the select field

<select onChange={handleSelectItem}>
    {fruits
     .filter((item) => !selectedItems.find(fruit=> item))
     .map((item) => <option value={item}>{item}</option>)}
</select>

Here filter function will keep those value only which are not in the selectedItems array.

When you select an option this handle function will be executed:

const handleSelectItem = (e) => {
    setSelectedItems([...selectedItems, e.target.value])
}

This function will add new item to the selectedItems array.

Using this procedure you will get the array you selected from the fruits with less code.

You can store the selection status (if and by in which dropdown an item has been selected) in a state and provide only non-selected options (on top of the currently selected value).

I worked with a default selection (Apple, Banana, Pear via selectedIn in the state initialization), of course you could also work with a default value along the lines of Select your fruit to force the user to make a choice.

const { useState } = React;

const App = () => {
  const [options, setOptions] = useState([
    { label: "Apple", selectedIn: 0 },
    { label: "Banana", selectedIn: 1 },
    { label: "Pear", selectedIn: 2 },
    { label: "Mango", selectedIn: undefined },
  ]);

  const handleChange = (value, dropdownId) => {
    const remainingOptions = options.filter(
      (o) => o.label !== value && o.selectedIn !== dropdownId
    );

    //free up value previously selected in that dropdown
    const optionToFreeUp = options.find((o) => o.selectedIn === dropdownId);
    if (optionToFreeUp !== undefined) {
      optionToFreeUp.selectedIn = undefined;
      remainingOptions.push(optionToFreeUp);
    }

    setOptions([...remainingOptions, { label: value, selectedIn: dropdownId }]);
  };

  const renderDropdown = (dropdownId) => {
    return (
      <div>
        Fruits dropdown {dropdownId}:
        <select
          onChange={(e) => handleChange(e.target.value, dropdownId)}
          value={
            (
              options.find((o) => o.selectedIn === dropdownId) || {
                label: "none",
              }
            ).label
          }
        >
          {renderOptions(dropdownId)}
        </select>
      </div>
    );
  };

  //render options that are selected by the current dropdown or unselected
  const renderOptions = (dropdownId) =>
    options
      .filter((o) => o.selectedIn === dropdownId || o.selectedIn === undefined)
      .map((o) => <option value={o.label}>{o.label}</option>);

  //render 3 different dropdowns with a static array so each dropdown gets an ID
  return (
    <div>
      {[0, 1, 2].map((id) => renderDropdown(id))}
      <p>
        Current selection:{" "}
        {options.filter((o) => o.selectedIn !== undefined).map((o) => o.label + " ")}
      </p>
    </div>
  );
};

// Render it
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

You could store the selected items and use it as comparison reference when renders the dropdowns.

import * as React from "react";

let dropdowns = [
  {
    name: "dropdown1",
    options: ["apple", "banana", "pineapple", "pear", "mango"]
  },
  {
    name: "dropdown2",
    options: ["apple", "banana", "pineapple", "pear", "mango"]
  },
  {
    name: "dropdown3",
    options: ["apple", "banana", "pineapple", "pear", "mango"]
  }
];

export default function App() {
  const [selected, setSelected] = React.useState([]);

  return (
    <div className="App">
      {dropdowns.map((dropdown, index) => {
        const dropdownItem =
          selected &&
          selected.length &&
          selected.filter((selectedItem) => !!selectedItem[dropdown.name]);
        const dropdownItemName =
          (dropdownItem && dropdownItem[0] && dropdownItem[0][dropdown.name]) ||
          "";
        const key = `dropdown-${dropdown.name}-${index}`;

        return (
          <select
            key={key}
            name={dropdown.name}
            value={dropdownItemName}
            onChange={(event) => {
              const newSelected = selected.length
                ? selected.map((selectedItem) => {
                    if (selectedItem[dropdown.name]) {
                      const newSelectedItem = {
                        [dropdown.name]: event.target.value
                      };

                      return newSelectedItem;
                    }

                    return selectedItem;
                  })
                : [{ [dropdown.name]: event.target.value }];

              if (
                !newSelected.filter(
                  (filteredItem) => !!filteredItem[dropdown.name]
                ).length
              ) {
                newSelected.push({ [dropdown.name]: event.target.value });
              }

              setSelected(newSelected);
            }}
          >
            <option></option>
            {dropdown.options
              .filter((option) => {
                if (selected.length) {
                  return !selected.filter(
                    (selectedItem) =>
                      selectedItem[Object.keys(selectedItem)[0]] === option &&
                      Object.keys(selectedItem)[0] !== dropdown.name
                  ).length;
                }

                return true;
              })
              .map((option, index) => {
                const key = `dropdown-${dropdown.name}-${option}-${index}`;

                return (
                  <option key={key} value={option}>
                    {option}
                  </option>
                );
              })}
          </select>
        );
      })}
    </div>
  );
}

Codesandbox Link: https://codesandbox.io/s/multiple-dropdowns-shared-state-react-e2mtb?file=/src/App.js

Related