React Material UI - How to automatically remove items from an Autocomplete depending on another value

Viewed 433

I have 2 select boxes that i am building uisng the autocomplete component in material ui - https://mui.com/material-ui/react-autocomplete/#checkboxes

I have a edge case im trying to fix:

  1. A user selects both "Consumer Electronics" and "Fashion & Apparel" in the first main industries select box.
  2. A user then selects in the second sub industries select box, "Computer & Laptops" and "Eyewear". (1 from each group).
  3. If a user then removes "Fashion & Apparel" from the first select box, all of the items that were selected with that category should be removed in the second select box.

enter image description here

example:

https://codesandbox.io/s/checkboxestags-demo-material-ui-forked-qox19g?file=/demo.tsx

You can see in the second "Autocomplete" im using the "filterOptions" to only show options depending on the first "Autocomplete"

also see data.json in the example for the raw data.

code:

    return (
      <Autocomplete
        multiple
        options={subIndustries}
        filterOptions={(x) =>
          x.filter((x) =>
            getValues("main_industries").find((m) => m.name === x.category)
          )
        }
        groupBy={(option) => option.category}
        disableCloseOnSelect
        getOptionLabel={(option) => option.name}
        onChange={(_, newValue: any) => {
          setValue("sub_industries", newValue, { shouldValidate: true });
        }}
        defaultValue={getValues("sub_industries")}
        onInputChange={(_, val) => {
          console.log(val);
        }}
        renderOption={(props, option, { selected }) => (
          <li {...props}>
            <Checkbox
              icon={uncheckedIcon}
              checkedIcon={checkedIcon}
              style={{ marginRight: 8 }}
              checked={selected}
            />
            {option.name}
          </li>
        )}
        renderInput={(params) => (
          <TextField
            {...params}
            label="Sub Industries"
            placeholder="Sub Industries"
          />
        )}
      />
    );
  };
1 Answers

Before all, some points related to your code:

  1. On Autcocomplete of SubIndustrySelector function, you should use value instead defaultValue. defaultValue should be use when component is not controlled.

  2. You also need to include the isOptionEqualToValue prop to both Autocomplete . Your code claims to use it. According to Mui docs, isOptionEqualToValue is necessary for:

Used to determine if the option represents the given value. Uses strict equality by default. ⚠️ Both arguments need to be handled, an option can only match with one value.

So you just need to guarantee that the option is equal the value with:
isOptionEqualToValue={(option, value) => option.id === value.id}

Related to the issue you are facing, the SubIndustrySelector isn't get update because you are controlling what tags should render using getValues instead watch from react-hook-form.

According to react-hook-form docs, getValues are:

An optimized helper for reading form values. The difference between watch and getValues is that getValues will not trigger re-renders or subscribe to input changes

(You can check more about it here and here.)

So, your SubIndustrySelector function should be something like this:

// WATCH ON sub_industries
  const watchSubIndustries = watch("sub_industries");

  const SubIndustrySelector = () => {
    if (!dataJson) {
      return <Skeleton height={"100%"} variant="text" />;
    }

    const subIndustries = dataJson
      .map((i) => {
        return i.industries;
      })
      .flat()
      .filter(Boolean);

    return (
      <Autocomplete
        multiple
        options={subIndustries}
        filterOptions={(x) =>
          x.filter((x) =>
            getValues("main_industries").find((m) => m.name === x.category)
          )
        }
        groupBy={(option) => option.category}
        disableCloseOnSelect
        getOptionLabel={(option) => option.name}
        // isOptionEqualToValue CONTROL
        isOptionEqualToValue={(option, value) => option.id === value.id}
        onChange={(_, newValue: any) => {
          setValue("sub_industries", newValue, { shouldValidate: true });
        }}
        //defaultValue={watchSubIndustries}
        value={watchSubIndustries}
        renderOption={(props, option, { selected }) => {
          return (
            <li {...props}>
              <Checkbox
                icon={uncheckedIcon}
                checkedIcon={checkedIcon}
                style={{ marginRight: 8 }}
                checked={selected}
              />
              {option.name}
            </li>
          );
        }}
        renderInput={(params) => {
          return (
            <TextField
              {...params}
              label="Sub Industries"
              placeholder="Sub Industries"
            />
          );
        }}
      />
    );
  };

You can check the code above working in this code sample.

As you can see, the changes that i did are:

  1. before the SubIndustrySelector function i added a watch to sub_industries -> watchSubIndustries.
  2. Added isOptionEqualToValue prop
  3. Changed defaultValue to value prop and add watchSubIndustries as value.

If you have any questions just ask in the comments.

Related