0

I have a drop down with check boxes inside it (used Bootstrap). When two are checked, I want to make it so the users cannot check anymore until the others are unchecked. I attempted this using the disable attribute but keep getting the error below.

What I tried:

export default function Test() {
const [countries, setCountries] = useState([]);
const [disableRadio, setDisableRadio] = useState(false);

function createCountryList() {
    const countries = ["a", "b", "c", "d"]; 
    return countries.map((country, key) => (
        <Form.Check
            disabled={disableRadio ? (e) => e.target.checked : false}
            key={key}
            label={country}
            onChange={(e) =>
                e.target.checked ? handleChecked(e) : handleUncheck(e)
            }
        />
    ));
}

function handleChecked(e) {
    const Arr = countries;
    Arr.push(e.target.value);
    setCountries(Arr);
    if (countries.length > 1) {
        setDisableRadio(true);
    } else {
        setDisableRadio(false);
    }
}

function handleUncheck(e) {
    const Arr = countries;
    const index = Arr.indexOf(e.target.value);
    Arr.splice(index, 1);
    setCountries(Arr);
}

return (
    <>
        <Dropdown>
            <Dropdown.Menu as={CustomMenu}>
                {createCountryList()}
            </Dropdown.Menu>
        </Dropdown>
    </>
);

}

Getting this error: react-dom.development.js:67 Warning: Invalid value for prop 'disabled' on <input> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.

What I want:

enter image description here

Thanks!

HarryS
  • 39
  • 4
  • This line doesn't really make sense: `disabled={disableRadio ? (e) => e.target.checked : false}`. The prop only expects a boolean: why are you passing a function in? – Terry Feb 14 '22 at 18:24
  • I use the arrow function because I wanted to see if the check box is checked or not. Sorry if I misunderstood how this all works. How would I go about doing this? – HarryS Feb 14 '22 at 18:32

1 Answers1

2

There are several issues with your code, and I have a feeling that you're overcomplicating the handling of checkbox events.

  1. You have countries as a state, and also as a fixed array of available countries. I'd suggest you store them separately: one is pure data (fixed array of available countries), and the other is the component state.

  2. You can use Set to handle adding/removing of countries, without too much of a fuss. There is no need to have separate event handlers for checked/unchecked states. We can handle adding/removing selected countries to the array using this logic below:

    const [countries, setCountries] = useState(new Set());
    
    function onChange(e) {
      if (e.target.checked) {
        setCountries(c => new Set([...c, e.target.value]));
      } else {
        setCountries(c => new Set([...c].filter(x => x !== e.target.value)))
      }
    }
    
  3. There is no need to store disableRadio in a separate state. Instead, take advantage of useMemo, or simply use the logic of countries.size > 1 in the template itself. Combine that with countries.has(country) to check whether a given checkbox's value is in the set of values. In this sense, a checkbox is disabled when (1) more than 1 countries have been selected AND (2) the count is NOT in the list of selected countries

    disabled={countries.size > 1 && !countries.has(country)}
    
  4. Your checkbox options are missing the value attribute: remember to add that.


With all these changes proposed, your code can be simplified into something like this:

export default function Test() {
  // NOTE: Store available countries as a constant
  const availableCountries = ["a", "b", "c", "d"];

  // NOTE: countries here refer to SELECTED countries
  //       We use new Set() so we automatically dedupe values
  const [countries, setCountries] = useState(new Set());

  function createCountryList() {
    return availableCountries.map((country, key) => (
      <Form.Check
        disabled={countries.size > 1 && !countries.has(country)}
        key={key}
        value={country}
        label={country}
        onChange={onChange}
      />
    ));
  }

  function onChange(e) {
    if (e.target.checked) {
      setCountries(c => new Set([...c, e.target.value]));
    } else {
      setCountries(c => new Set([...c].filter(x => x !== e.target.value)))
    }
  }

  return (
    <>
      <Dropdown>
        <Dropdown.Menu as={CustomMenu}>
          {createCountryList()}
        </Dropdown.Menu>
      </Dropdown>
    </>
  );
}

See example of CodeSandbox:

Edit purple-microservice-9ksrm

Terry
  • 63,248
  • 15
  • 96
  • 118