From 0e50a61ca3df5b19be87b913e950bb0247f541e3 Mon Sep 17 00:00:00 2001 From: Kevin Wu Date: Wed, 4 Dec 2024 23:09:45 -0800 Subject: [PATCH] refactor: move search state to context --- src/app/search/layout.tsx | 5 ++ src/components/search/Search.tsx | 90 +++---------------- .../filter/search-filter-date-select.tsx | 42 +++++---- .../search-filter-institution-dropdown.tsx | 14 +-- .../filter/search-filter-sort-dropdown.tsx | 11 ++- .../search/filter/search-filter.tsx | 42 ++------- src/components/search/search-blurb.tsx | 5 +- .../search-context/search-context.tsx | 58 ++++++++++++ .../search-context/use-search-state.tsx | 48 ++++++++++ 9 files changed, 164 insertions(+), 151 deletions(-) create mode 100644 src/app/search/layout.tsx create mode 100644 src/contexts/search-context/search-context.tsx create mode 100644 src/contexts/search-context/use-search-state.tsx diff --git a/src/app/search/layout.tsx b/src/app/search/layout.tsx new file mode 100644 index 0000000..02f6d83 --- /dev/null +++ b/src/app/search/layout.tsx @@ -0,0 +1,5 @@ +import { SearchContextProvider } from "@/contexts/search-context/search-context"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/src/components/search/Search.tsx b/src/components/search/Search.tsx index 1cf6779..46207b7 100644 --- a/src/components/search/Search.tsx +++ b/src/components/search/Search.tsx @@ -1,14 +1,12 @@ "use client"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback } from "react"; import { SearchFilterDialog } from "@/components/search/filter/search-filter-dialog"; import { SearchFilterSortDropdown } from "@/components/search/filter/search-filter-sort-dropdown"; import { SearchBlurb } from "@/components/search/search-blurb"; import { SearchResults } from "@/components/search/search-results"; -import type { - CourseObject, - FilterValues, -} from "@/components/search/search.types"; +import type { CourseObject } from "@/components/search/search.types"; +import { useSearchContext } from "@/contexts/search-context/search-context"; import { UNIVERSITY_GE } from "@/lib/constants"; import { useQueryState } from "nuqs"; @@ -27,6 +25,8 @@ export function Search({ courses: CourseObject[]; lastUpdated: number; }) { + const { filterValues } = useSearchContext(); + const [university, setUniversity] = useQueryState("uni", { defaultValue: _university, shallow: false, @@ -38,53 +38,6 @@ export function Search({ clearOnDefault: false, }); - const [format, setFormat] = useState([true, true]); - const [enrollment, setEnrollment] = useState([true]); - const [available, setAvailable] = useState([true]); - const [start, setStart] = useState(); - const [end, setEnd] = useState(); - const [institution, setInstitution] = useState("Any Institution"); - const [min, setMin] = useState(0); - const [max, setMax] = useState(20); - - const [sort, setSort] = useState("Default Sort"); - - const [filterValues, setFilterValues] = useState({ - format: format, - enrollment: enrollment, - available: available, - start: start, - end: end, - institution: institution, - min: min, - max: max, - sort: sort, - }); - - useEffect(() => { - setFilterValues({ - format, - enrollment, - available, - start, - end, - institution, - min, - max, - sort, - }); - }, [ - format, - enrollment, - available, - start, - end, - institution, - min, - max, - sort, - ]); - const handleUniversityChange = useCallback( (university: string) => { setUniversity(university); @@ -100,6 +53,8 @@ export function Search({ [setGE] ); + const results = filterData(courses, filterValues); + return (
@@ -126,46 +81,21 @@ export function Search({
- +
- + diff --git a/src/components/search/filter/search-filter-date-select.tsx b/src/components/search/filter/search-filter-date-select.tsx index eb747d3..49cc4ef 100644 --- a/src/components/search/filter/search-filter-date-select.tsx +++ b/src/components/search/filter/search-filter-date-select.tsx @@ -1,40 +1,38 @@ +import { useCallback } from "react"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { useSearchContext } from "@/contexts/search-context/search-context"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { CalendarIcon } from "lucide-react"; import { Button } from "../../ui/button"; -interface SearchFilterDateSelectProps { - onStartChange: (input: Date | undefined) => void; - onEndChange: (input: Date | undefined) => void; - start: Date | undefined; - end: Date | undefined; -} +export function SearchFilterDateSelect() { + const { start, setStart, end, setEnd } = useSearchContext(); -export function SearchFilterDateSelect({ - onStartChange, - onEndChange, - start, - end, -}: SearchFilterDateSelectProps) { - const handleStartChange = (date: Date | undefined) => { - if (!date) { - console.error("No start date selected"); - return; - } + const handleStartChange = useCallback( + (date: Date | undefined) => { + if (!date) { + console.error("No start date selected"); + return; + } - onStartChange(date); - }; + setStart(date); + }, + [setStart] + ); - const handleEndChange = (date: Date | undefined) => { - onEndChange(date); - }; + const handleEndChange = useCallback( + (date: Date | undefined) => { + setEnd(date); + }, + [setEnd] + ); return (
diff --git a/src/components/search/filter/search-filter-institution-dropdown.tsx b/src/components/search/filter/search-filter-institution-dropdown.tsx index 5859c85..c7aee3f 100644 --- a/src/components/search/filter/search-filter-institution-dropdown.tsx +++ b/src/components/search/filter/search-filter-institution-dropdown.tsx @@ -6,19 +6,19 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { useSearchContext } from "@/contexts/search-context/search-context"; interface SearchFilterInstitutionDropdownProps { - value: string; - onChange: (input: string) => void; courses: CourseObject[] | undefined; } export function SearchFilterInstitutionDropdown({ - value, - onChange, courses, }: SearchFilterInstitutionDropdownProps) { - const uniqueColleges = value == "Any Institution" ? [] : [value]; + const { institution, setInstitution } = useSearchContext(); + + const uniqueColleges = + institution == "Any Institution" ? [] : [institution]; const sendingInstitutions = courses?.map( (course: CourseObject) => course.sendingInstitution @@ -40,8 +40,8 @@ export function SearchFilterInstitutionDropdown({ Teaching Institution
diff --git a/src/components/search/filter/search-filter.tsx b/src/components/search/filter/search-filter.tsx index 593d1d4..423707b 100644 --- a/src/components/search/filter/search-filter.tsx +++ b/src/components/search/filter/search-filter.tsx @@ -1,32 +1,15 @@ -import { Dispatch, SetStateAction } from "react"; import { SearchFilterCheckbox } from "@/components/search/filter/search-filter-checkbox"; import { SearchFilterDateSelect } from "@/components/search/filter/search-filter-date-select"; import { SearchFilterInstitutionDropdown } from "@/components/search/filter/search-filter-institution-dropdown"; -import { CourseObject, FilterValues } from "@/components/search/search.types"; +import { CourseObject } from "@/components/search/search.types"; +import { useSearchContext } from "@/contexts/search-context/search-context"; interface SearchFilterProps { - setFormat: Dispatch>; - setEnrollment: Dispatch>; - setAvailable: Dispatch>; - setStart: Dispatch>; - setEnd: Dispatch>; - setInstitution: Dispatch>; - setMin: Dispatch>; - setMax: Dispatch>; - filterValues: FilterValues; courses: CourseObject[] | undefined; } -export const SearchFilter = (props: SearchFilterProps) => { - const { - setFormat, - setAvailable, - setStart, - setEnd, - setInstitution, - filterValues, - courses, - } = props; +export const SearchFilter = ({ courses }: SearchFilterProps) => { + const { format, setFormat, available, setAvailable } = useSearchContext(); return (
@@ -37,27 +20,18 @@ export const SearchFilter = (props: SearchFilterProps) => {
- - + +
); diff --git a/src/components/search/search-blurb.tsx b/src/components/search/search-blurb.tsx index f011087..3f942a5 100644 --- a/src/components/search/search-blurb.tsx +++ b/src/components/search/search-blurb.tsx @@ -3,6 +3,7 @@ import type { CourseObject, FilterValues, } from "@/components/search/search.types"; +import { useSearchContext } from "@/contexts/search-context/search-context"; interface SearchBlurbProps { filterData: ( @@ -11,15 +12,15 @@ interface SearchBlurbProps { ) => CourseObject[]; courses: CourseObject[] | undefined; lastUpdated: number | undefined; - filterValues: FilterValues; } export const SearchBlurb = ({ filterData, courses, lastUpdated, - filterValues, }: SearchBlurbProps) => { + const { filterValues } = useSearchContext(); + const [timeAgo, setTimeAgo] = useState(""); const getTimeAgo = useCallback((date: number) => { diff --git a/src/contexts/search-context/search-context.tsx b/src/contexts/search-context/search-context.tsx new file mode 100644 index 0000000..6229dca --- /dev/null +++ b/src/contexts/search-context/search-context.tsx @@ -0,0 +1,58 @@ +"use client"; + +import React, { + createContext, + useContext, + type Dispatch, + type SetStateAction, +} from "react"; +import { FilterValues } from "@/components/search/search.types"; +import { useSearchState } from "@/contexts/search-context/use-search-state"; + +interface SearchContextProps { + format: boolean[]; + setFormat: Dispatch>; + enrollment: boolean[]; + setEnrollment: Dispatch>; + available: boolean[]; + setAvailable: Dispatch>; + start?: Date; + setStart: Dispatch>; + end?: Date; + setEnd: Dispatch>; + institution: string; + setInstitution: Dispatch>; + min: number; + setMin: Dispatch>; + max: number; + setMax: Dispatch>; + sort: string; + setSort: Dispatch>; + filterValues: FilterValues; +} + +const SearchContext = createContext(undefined); + +export const SearchContextProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const value = useSearchState(); + + return ( + + {children} + + ); +}; + +export const useSearchContext = (): SearchContextProps => { + const context = useContext(SearchContext); + if (!context) { + throw new Error( + "useSearchContext must be used within a SearchContextProvider" + ); + } + return context; +}; diff --git a/src/contexts/search-context/use-search-state.tsx b/src/contexts/search-context/use-search-state.tsx new file mode 100644 index 0000000..f855474 --- /dev/null +++ b/src/contexts/search-context/use-search-state.tsx @@ -0,0 +1,48 @@ +import { useState } from "react"; +import { FilterValues } from "@/components/search/search.types"; + +export function useSearchState() { + const [format, setFormat] = useState([true, true]); + const [enrollment, setEnrollment] = useState([true]); + const [available, setAvailable] = useState([true]); + const [start, setStart] = useState(); + const [end, setEnd] = useState(); + const [institution, setInstitution] = useState("Any Institution"); + const [min, setMin] = useState(0); + const [max, setMax] = useState(20); + const [sort, setSort] = useState("Default Sort"); + + const filterValues: FilterValues = { + format, + enrollment, + available, + start, + end, + institution, + min, + max, + sort, + }; + + return { + format, + setFormat, + enrollment, + setEnrollment, + available, + setAvailable, + start, + setStart, + end, + setEnd, + institution, + setInstitution, + min, + setMin, + max, + setMax, + sort, + setSort, + filterValues, + }; +}