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!