Search code examples
javascriptreactjstypescript

How to place filtered values into dropdown


In my React web application, I'm trying to implement a feature where selecting a Province filters the options in the City dropdown to only display cities belonging to that Province. However, there's an issue where upon selecting a Province, the City dropdown automatically scrolls to the first city listed under that Province, while still displaying all cities from other Provinces.

I'm trying to only display the cities that belong to a province when selected, not show all provinces.

I tried filtering/mapping over the filteredData, and then setting the cityDropdownFilter state to filteredCities[0] (so it would only show the filtered cities from the selected province and nothing else), but that's not working.

Here's the snippet of code where I'm trying to implement the logic, which lives inside of my "Header.tsx" component:

const handleDropdownFilterProvince = (
    e: React.ChangeEvent<HTMLSelectElement>
  ) => {
    useFilterStore.setState({ provinceDropdownFilter: e.target.value });

    // Filter the data based on the selected province
    const filteredCitiesSet = new Set<string>(
      filteredData
        .filter((item) => item.state === e.target.value)
        .map((item) => item.city)
    );

    const filteredCities = Array.from(filteredCitiesSet);

    useFilterStore.setState({ cityDropdownFilter: filteredCities[0] });
    console.log("handle province dropdown cities", filteredCities);

    // Clear out other cities from dropdown that don't match selected province
    // ^^^ need to figure out logic here
  };

For context, here's the full code below of the Header component:

Header.tsx:

export const Header = ({ data, isLoading, error }: HeaderProps) => {
 
  ...
  // Bring in global state
  const {
    manufacturerDropdownFilter,
    provinceDropdownFilter,
    cityDropdownFilter,
    priceRangeFilter,
  } = useFilterStore();

  const filteredData = data.map((group) => group.equipments).flat();

  ...

  const handleDropdownFilterProvince = (
    e: React.ChangeEvent<HTMLSelectElement>
  ) => {
    useFilterStore.setState({ provinceDropdownFilter: e.target.value });

    // Filter the data based on the selected province
    const filteredCitiesSet = new Set<string>(
      filteredData
        .filter((item) => item.state === e.target.value)
        .map((item) => item.city)
    );

    const filteredCities = Array.from(filteredCitiesSet);

    useFilterStore.setState({ cityDropdownFilter: filteredCities[0] });
    console.log("handle province dropdown cities", filteredCities);

    // Clear out other cities from dropdown that don't match selected province
    // ^^^ need to figure out logic here
  };

  const handleDropdownFilterCity = (
    e: React.ChangeEvent<HTMLSelectElement>
  ) => {
    useFilterStore.setState({ cityDropdownFilter: e.target.value });
  };

  ...

  return (
    <>
    ...
          <Dropdown
            data={filteredData}
            value={provinceDropdownFilter}
            onChange={handleDropdownFilterProvince}
            filterType="state"
          />
          <Dropdown
            data={filteredData}
            value={cityDropdownFilter}
            onChange={handleDropdownFilterCity}
            filterType="city"
          />
    ...
    </>
  );
};

And full code for the Dropdown component:

Dropdown.tsx:

export const Dropdown: React.FC<DropdownProps> = ({
  data,
  onChange,
  value,
  filterType,
}) => {
  
  ...

  // Filter the data based on the filterType
  const filteredOptions = data.filter((i) => {
    if (filterType === "manufacturer") {
      return i.manufacturer;
    }
    if (filterType === "state") {
      return i.state;
    }
    if (filterType === "city") {
      return i.city;
    }

    return false;
  });

  // Remove duplicate values to show each value only once (e.g., "TX", "Los Angeles") instead of fetching every instance from the API.
  const uniqueValues = new Set<string>();

  for (let i = 0; i < filteredOptions.length; i++) {
    uniqueValues.add(filteredOptions[i][filterType] as string);
  }

  return (
    <>
      <Select
        aria-label="Select"
        placeholder={getCustomPlaceholder(filterType) as string}
        value={value}
        onChange={handleFilters}
      >
        {Array.from(uniqueValues)
          .sort()
          .map((value) => (
            <option key={value} value={value}>
              {value}
            </option>
          ))}
      </Select>
    </>
  );
};

Solution

  • The problem

    cityDropdownFilter is being used as the dropdown value value={cityDropdownFilter}" then you are setting the dropdown value based on a different set of cities to what is in the dropdown data.

            <Dropdown
                data={filteredData}
                value={cityDropdownFilter}
                onChange={handleDropdownFilterCity}
                filterType="city"
              />
    

    The solution

    Filter the dropdown data instead:

          <Dropdown
                data={filteredData.filter((item) => item.state === provinceDropdownFilter)}
                value={cityDropdownFilter}
                onChange={handleDropdownFilterCity}
                filterType="city"
              />