Using multiple dropdown filters simultaneously

Hi,

I am building a project which has three different dropdowns, each with multiple checkboxes, to filter on different items.

Individually, either one of them does what I intend, however, I can. not get them to work in combination (e.g. I can filter for office locations but. not for office locations AND company name).

Here is where I create the basic logic of the filters:

'use client'
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import recruits from '@/mockData/recruit.json';

import { useRecruits } from "@/Context/RecruitsContext";


export const UseSelectedParams = () => {
  // const { recruits } = useRecruits();
  const router = useRouter();
  const searchParams = useSearchParams();

  const [selectedOffices, setSelectedOffices] = useState<string[]>([]);
  const [selectedRecruits, setSelectedRecruits] = useState<string[]>([]);
  const [selectedCompanies, setSelectedCompanies] = useState<string[]>([]);

  const sortedOffices = [...recruits].sort((a, b) => a.officeName.localeCompare(b.officeName));
  const sortedRecruiters = [...recruits].sort((a, b) => a.recruiterName.localeCompare(b.recruiterName));
  const sortedCompanies = [...recruits].sort((a, b) => a.companyName.localeCompare(b.companyName));

  const uniqueOfficeNames = new Set();
  const uniqueRecruiterNames = new Set();
  const uniqueCompanyNames = new Set();

  const uniqueOffices = sortedOffices.filter(office => {
    if (!uniqueOfficeNames.has(office.officeName)) {
      uniqueOfficeNames.add(office.officeName);
      return true;
    }
    return false;
  });

  const uniqueRecruiters = sortedRecruiters.filter(recruiter => {
    if (!uniqueRecruiterNames.has(recruiter.recruiterName)) {
      uniqueRecruiterNames.add(recruiter.recruiterName);
      return true;
    }
    return false;
  });

  const uniqueCompanies = sortedCompanies.filter(company => {
    if (!uniqueCompanyNames.has(company.companyName)) {
      uniqueCompanyNames.add(company.companyName);
      return true;
    }
    return false;
  });


  const handleOfficeCheckboxChange = (officeId: string) => {
    setSelectedOffices(prev => prev.includes(officeId) ? prev.filter(id => id !== officeId) : [...prev, officeId,]);
  };

  const handleRecruitSelectionChange = (recruitId: string) => {
    setSelectedRecruits(prev => prev.includes(recruitId) ? prev.filter(id => id !== recruitId) : [...prev, recruitId]);
  };

  const handleCompaniesSelectionChange = (companyId: string) => {
    setSelectedCompanies(prev => prev.includes(companyId) ? prev.filter(id => id !== companyId) : [...prev, companyId]);
  };

  useEffect(() => {
    const params = new URLSearchParams();

    if (selectedOffices.length > 0) {
      params.append('offices', selectedOffices.join(','));
    }

    if (selectedRecruits.length > 0) {
      params.append('recruiters', selectedRecruits.join(','));
    }

    if (selectedCompanies.length > 0) {
      params.append('companies', selectedCompanies.join(','));
    }

    const query = params.size ? params.toString() : '';
    const path = `/recruits${query ? `?${query}` : '' }`;

    const timer = setTimeout(() => {
      router.replace(path);
    }, 100);

    return () => clearTimeout(timer);
  }, [selectedOffices, selectedRecruits, selectedCompanies]);


  return {
    handleOfficeCheckboxChange,
    handleRecruitSelectionChange,
    handleCompaniesSelectionChange,
    uniqueOffices,
    uniqueRecruiters,
    uniqueCompanies,
  };
};

Here is where they are use (just showing one of the filters to save space)

import React from 'react';
import { useTranslations } from 'next-intl';
import { UseSelectedParams } from './UseSelectedParams';

const ListOffices = () => {
  const t = useTranslations();
  const { handleOfficeCheckboxChange, uniqueOffices } = UseSelectedParams();

  return (
    <section className='w-fit mr-5 h-16 z-10'>
      <details className="dropdown bg-consid-black bg-opacity-95 text-white p-2 w-56">
        <summary className="m-1 btn cursor-pointer font-semibold">{t('filters.FilterOffice')}</summary>
        <ul className="p-2 shadow menu dropdown-content bg-base-100 rounded-box w-52">
          {uniqueOffices.map(office => (
            <React.Fragment key={office.officeId}>
              <div className='form-control flex justify-between'>
                <label htmlFor={office.officeId} className='cursor-pointer'>{office.officeName}</label>
                <input
                  type="checkbox"
                  id={office.officeId}
                  className="checkbox cursor-pointer"
                  onChange={() => handleOfficeCheckboxChange(office.officeId)}
                />
              </div>
            </React.Fragment>
          ))}
        </ul>
      </details>
    </section>
  );
}

export default ListOffices

And here is where I implement it:


'use client'
import { useRecruits } from "@/Context/RecruitsContext";
import RecruitCardList from '@/app/[locale]/components/Recruit/RecruitCardList';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import FiltersList from '../components/Filters/FiltersList';

const Home = ({searchParams}: {searchParams: string[]}) => {

  const { recruits, isLoading } = useRecruits();

  const t = useTranslations();

  const [filteredRecruits, setFilteredRecruits] = useState<any[]>([]);

  useEffect(() => {
    const sortedRecruits = recruits.sort((a, b) => {
      const startDateA = new Date(a.startDate).getTime();
      const startDateB = new Date(b.startDate).getTime();

      return startDateA - startDateB;
    });
    const searchParams = new URLSearchParams(window.location.search);

    const selectedOffices = searchParams.get('offices')?.split(',') || [];
    const selectedRecruiters = searchParams.get('recruiters')?.split(',') || [];
    const selectedCompanies = searchParams.get('companies')?.split(',') || [];

    const combinedFilteredRecruits = sortedRecruits.filter(recruit =>
      (selectedOffices.length === 0 || selectedOffices.includes(recruit.officeId)) &&
      (selectedRecruiters.length === 0 || selectedRecruiters.includes(recruit.recruiterId)) &&
      (selectedCompanies.length === 0 || selectedCompanies.includes(recruit.companyId))
    );
    setFilteredRecruits(combinedFilteredRecruits);
  }, [searchParams, recruits]);
  if (isLoading === true) {
    return (
    <button type="button" className="bg-indigo-500" disabled>
      <svg className="animate-spin h-5 w-5 mr-3" viewBox="0 0 24 24"></svg>
      Loading...
    </button>
    );
  } else {
    return (
      <div className='space-y-6 w-full'>
        <p className='text-center font-bold text-2xl'>{t('recruits.NumberOfRecruits')}: {filteredRecruits.length}</p>
        <FiltersList />
        <div className="flex justify-start">
          <main className="flex flex-wrap gap-4 justify-between items-start">
                {filteredRecruits.map((recruit) => (
                  <RecruitCardList key={recruit.id} recruit={recruit} />
                ))}
          </main>
        </div>
      </div>
    )
  }
}

export default Home;

Much of it comes from the Next course, but the implementation for multiple filters used by Mosh is not applicable to my situation. Or I am simply not understanding how to implement it. Super thankfull for any help!