한번만 백엔드에서 가져와서
프론트에서 완전히 다뤄주는 코드
검색을 했을 때 그 검색 내역을 페이지에 url로 저장하는 법 - querystring을 이용하면 된다.
그냥 필터하는 부분에 navigate를 더해주었다.
리팩토링 전 코드
import { useState, useEffect } from "react";
import Select from "react-select";
import SearchList from "./SearchList";
import { useGetCourseQuery } from "../../redux/modules/apiSlice";
import { useDispatch, useSelector } from "react-redux";
import { changeHashTagNum } from "../../redux/modules/searchSlice";
import { hashTagOptions } from "../post/PostHashTag";
import { regionOptions } from "../post/PostCategories";
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
import { useNavigate } from "react-router";
const travelStatusOptions = [
{ value: false, label: "여행 전" },
{ value: true, label: "여행 후" },
];
const SearchBox = () => {
const navigate = useNavigate();
const linkedHashtagNum = useSelector(
(state: any) => state.searchSlice.hashtagNumber
);
const dispatch = useDispatch();
const { data, isLoading, isError } = useGetCourseQuery();
const [locations, setLocations] = useState<optionType[] | null>([]);
const [hashtags, sethashtags] = useState<optionType[] | null>([]);
const [words, setWords] = useState("");
const [travelStatus, setTravelStatus] = useState<boolean>();
const [filteredList, setFilteredList] = useState<CourseType[]>();
//셀렉트한 데이터 State에 반영하기
const locationOnChangeHandler = (data: any) => {
setLocations(data);
};
const hashtagOnChangeHandler = (data: any) => {
sethashtags(data);
};
const travelStatusOnChangeHandler = (data: any) => {
setTravelStatus(data?.value);
};
//섹렉트된 데이터 형태 object에서 string[]으로 바꾸기
let locationsArr = locations?.map((item) => item.value);
let hashtagsArr = hashtags?.map((item) => item.label);
//sample 배열이 base배열의 부분 함수인지 여부 true, false로 반환하는 함수
const isSubsetOf = function (
base: string[] | undefined,
sample: string[] | undefined
) {
return sample?.every((item: any) => base?.includes(item));
};
// 선택된 검색 옵션이 없으면 전체 데이터 보여주기
const filterData = () => {
if (
words.length === 0 &&
locationsArr?.length === 0 &&
hashtagsArr?.length === 0 &&
travelStatus === undefined
) {
setFilteredList(data);
}
// 지역, 해시태그, 키워드(제목, 코스 각각의 이름, 지번주소, 도로명 주소, 메모), 여행 전/후 여부에 따라 필터링
else {
const filteredData: CourseType[] | undefined = data
?.filter((item) => isSubsetOf(item.location, locationsArr))
.filter((item) => isSubsetOf(item.hashtags, hashtagsArr))
.filter(
(item) =>
item.title.toLowerCase().includes(words.toLowerCase()) ||
JSON.parse(item.courseList).filter(
(item: CourseListType) =>
item.name.toLowerCase().includes(words.toLowerCase()) ||
item.address.toLowerCase().includes(words.toLowerCase()) ||
item.road.toLowerCase().includes(words.toLowerCase()) ||
item.memo?.toLowerCase().includes(words.toLowerCase())
).length !== 0
)
.filter((item) =>
travelStatus === undefined ? true : item.travelStatus === travelStatus
);
setFilteredList(filteredData);
}
};
//맨 처음 렌더링, 새로고침 할 때 전체 데이터 보여주기
useEffect(() => {
filterData();
}, [hashtags, locations, travelStatus, data]);
//메인페이지에서 해시태그 링크로 들어올 때 자동검색
//페이지 나갈 때 해시태그 자동검색 없애기
useEffect(() => {
if (linkedHashtagNum !== null) {
const linkSetHashtag = () => {
sethashtags([hashTagOptions[linkedHashtagNum]]);
};
linkSetHashtag();
}
return () => {
dispatch(changeHashTagNum(null));
};
}, []);
return (
<>
<div className="mb-[2%] 3xl:w-[60%] 2xl:w-[70%] w-[90%] min-w-[370px]">
<p className=" w-fit mx-auto xl:text-[55px] lg:text-[45px] sm:text-[35px] text-2xl font-bold my-[5%]">
WHAT ARE YOUR PLANS?
</p>
<div className="border border-black flex flex-col items-center gap-5 p-[40px]">
<div className="flex flex-row indent-2">
<div>지역</div>
<Select
options={regionOptions}
placeholder={"지역명"}
autoFocus={true}
isMulti
isSearchable={true}
isClearable={true}
onChange={locationOnChangeHandler}
value={locations}
/>
</div>
<div className="flex flex-row indent-2 ">
<div>해시태그</div>
<Select
options={hashTagOptions}
isMulti
isSearchable={true}
isClearable={true}
placeholder={"#해시태그"}
onChange={hashtagOnChangeHandler}
value={hashtags}
/>
</div>
<div className="flex flex-row indent-2 ">
<div>여행 전/후</div>
<Select
isClearable={true}
options={travelStatusOptions}
onChange={travelStatusOnChangeHandler}
/>
</div>
<div className="flex flex-row indent-2 ">
<div>검색어</div>
<input
className={
"rounded-sm indent-4 border border-gray-300 w-[90%] h-[38px]"
}
placeholder="입력하세요."
value={words}
onChange={(event) => setWords(event.target.value)}
/>
<button onClick={filterData}>검색</button>
</div>
</div>
</div>
{/* 나올 수 있는 리스트 상태 구분 */}
{filteredList?.length === 0 ? (
<p className="min-h-[1500px]">검색결과가 없습니다.</p>
) : isLoading ? (
<>
<div className="flex justify-between w-[60%] flex-wrap">
{new Array(12).fill(null).map((_, idx) => (
<SkeletonTheme
baseColor="#202020"
highlightColor="#444"
key={idx}
>
<div className=" mb-3 ">
<Skeleton width={300} height={300} />
<div className="mt-3">
<Skeleton width={200} height={30} />
<Skeleton width={50} height={25} />
<Skeleton width={150} height={15} />
</div>
</div>
</SkeletonTheme>
))}
</div>
</>
) : isError ? (
<p className="min-h-[1500px]">에러가 발생했습니다.</p>
) : (
<SearchList filteredList={filteredList} />
)}
</>
);
};
export default SearchBox;
querystring 이용해서 검색 기록을 유지하는 것을 하다가 너무 어려워서 중도 포기하고
firebase에서 배열이 포함되어 있는 경우에 가져오도록 하고 있는데, 빈 배열인 경우 에러가 나서 정말 곤란하다.
검색카테고리를 하나도 선택 안 했다면 차라리 괜찮은데
하나만 선택했을 경우에 다른 빈배열들이 오류를 일으킬 것이다. 이런 경우에 어떻게 해줘야할지... 빈배열은 왜 안되는거지 정말 불편하다. 나중에는 걸러서 가져오는것에 성공했다.
또 새로운 문제 array-contains-any 는 query 안에서 한번밖에 못 쓴다는것, 해시태그나 지역거르는 것 하나만 써야겠다.
그리고 firebase에서의 courseList 데이터가 전체 string으로 들어가있어서 어떻게 그걸 구별해줄지 막막하다.
https://redux-toolkit.js.org/rtk-query/usage/queries
rtk-query에서 여러개의 매개변수를 slice로 보내줄 때 queryoptions로 {key:value, key:value} 로 보낸다.
보내는 코드
const {
data: conditionData,
isLoading,
isError,
} = useGetCourseConditionallyQuery({
hashtags: hashtagsArr,
travelStatus: travelStatus === undefined ? "" : travelStatus,
});
slice에서 매개변수로 받아줄 때 구조분해 해서 가져오면 된다.
getCourseConditionally: builder.query<CourseType[], any>({
async queryFn({ hashtags, travelStatus }) {
try {
let courseQuery;
let courses: any = [];
if (hashtags.length === 0) {
courseQuery = query(
collection(dbService, "courses"),
where("travelStatus", "==", travelStatus),
orderBy("createdAt", "desc")
);
} else {
courseQuery = query(
collection(dbService, "courses"),
where("hashtags", "array-contains-any", hashtags),
where("travelStatus", "==", travelStatus),
orderBy("createdAt", "desc")
);
}
const querySnapshot = await getDocs(courseQuery);
querySnapshot?.forEach((doc) => {
courses.push({ id: doc.id, ...doc.data() } as CourseType);
});
return { data: courses };
} catch (error: any) {
console.error(error.message);
return { error: error.message };
}
},
providesTags: ["Courses"],
}),
array-contains-any를 써서 데이터베이스의 배열과 내가 검색한 데이터를 담은 배열을 비교하는데, 데이터 베이스의 배열의 요소가 내 배열 요소의 부분집합인 경우를 찾고 싶은데
내가 선택한 배열의 요소가 하나라도 들어가있으면 찾아와진다. 내가 원하는 방식으로 필터링이 안된다는거다. ;;;
결론적으로 필터링을 해서 가져올 수 있는거는 travelStatus 하나뿐이라는 비극이 이거 하나 딸랑 파이어베이스에서 필터링하는게 의미가 있나?
'개발자 되는 중 > TIL&WIL' 카테고리의 다른 글
내배캠 TIL 2023.03.04 (0) | 2023.03.04 |
---|---|
내배캠 TIL 2023.03.03 (0) | 2023.03.03 |
내배캠 TIL 2023.02.28 (2) | 2023.02.28 |
내배캠 TIL 2023.02.24 (0) | 2023.02.24 |
내배캠 TIL 2023.02.23 (0) | 2023.02.23 |