개발자 되는 중/TIL&WIL

내배캠 TIL 2023.03.02

SeonChoco 2023. 3. 2. 11:02

한번만 백엔드에서 가져와서 

프론트에서 완전히 다뤄주는 코드

 

검색을 했을 때 그 검색 내역을 페이지에 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

 

Queries | Redux Toolkit

RTK Query > Usage > Queries: fetching data from a server

redux-toolkit.js.org

 

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