[React] 다중필터 로직 짜는 법 & 구현하기 with query string

Hyodduru ·2022년 4월 24일
10

React

목록 보기
14/22
post-thumbnail

🤔 다중필터

이번 2차 프로젝트에서 쿼리스트링 관련한 기능 구현 부분을 담당했다.

그 중에서도 가장 기억에 남았던 다중필터!

구상한 로직, 코드 공유까지 해보고자 합니다--!

🧐 해결해야했던 문제

  1. 카테고리, 클래스 진행방식, 지역, 일정 총 4개의 필터가 있다.
  2. 필터 내의 필터 체크박스 항목을 누르고 '필터적용'이라는 버튼을 클릭함과 동시에 화면 상단의 url이 필터된 주소로 바뀌고, 백엔드로부터 필터된 정보를 요청한다.
  3. 체크박스가 클릭된 상태를 다시 해제한 채로 '필터적용'버튼을 눌렀을 때는 해당 부분의 코드가 없어지도록 하고, 그 부분 필터를 해제한다. (모든 정보를 보여준다)

🧚‍♀️ 다중필터 로직 구상하기

코드를 짜기 전에 전체적으로 어떤 흐름을 가지고 문제를 해결할지 순서대로 정리를 해보았다.

  1. 총 4개의 필터(카테고리, 클래스 진행방식, 지역, 일정)과 그 안의 체크 항목들을 담은 데이터를 객체를 담은 배열 형식으로 만들고, 이를 map을 활용하여 화면에 그려준다.

ex)

  {
    sort_type: 'schedules',
    title: '일정',
    contents: [
      '월요일',
        //코드 생략 
      '일요일',
    ],
  },

위의 객체가 여러개로 이루어진 배열의 형태

  1. 필터된 강의 카드 정보들을 받고 이를 cardList라는 state에 저장해주는 getCardListData라는 함수를 만든다.
    🔖 알아두기) 이 함수를 useEffect안에 넣어주고, getCardListData의 의존성 배열에는 useLocation().search를 넣어줌으로써 쿼리스트링 부분이 변경이 될 때마다 fetch를 하게끔 한다.
  const getCardListData = useCallback(async () => {
    const res = await fetch(
      `${BASE_URL}/main/search${search}`
    );

    const data = await res.json();

    setCardList(data.result);
  }, [search]);

  useEffect(() => {
    getCardListData();
  }, [getCardListData]);

🔖 참고)getCardListData함수를 useCallback으로 감싸줌으로써, 해당 컴포넌트가 랜더링되더라도 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환하게끔 한다.(최적화 작업)

  1. 필터 항목의 체크리스트를 누를 때마다 해당 카테고리의 체크리스트들을 배열의 형태로 저장할 'clickedCheckList'라는 state를 만든다.
  const [clickedCheckList, setClickedCheckList] = useState([]);
  1. handleCheckList라는 함수를 통해 해당 체크리스트가 클릭이되거나 해제가 되었을 때 그 해당 정보들을 clickedCheckList에 저장한다.
   const handleCheckList = (e, content, idx, sort_type) => {
    e.target.checked
      ? setClickedCheckList([
          ...clickedCheckList,
          { id: idx, content, sortType: sort_type },
        ])
      : setClickedCheckList(
          clickedCheckList.filter(list => list.content !== content)
        );
  };
  
    return 
  // 관련 없는 코드 생략
   {contents.map((content, idx) => (
                  <Content
                    key={idx}
                    onClick={e => handleCheckList(e, content, idx, sort_type)}
                  >
                    <input type="checkbox" />
                    {content}
                  </Content>
                ))}
  1. makeQueryString이라는 함수를 통해 '필터적용'이라는 버튼을 클릭을 했을 시 clickedCheckedList내에 있던 선택된 체크리스트들을 쿼리스트링 형태로 변경을 하고 navigate를 활용하여 이동해준다.
    (&category_id=1 과 같은 형태로 바꾸어줌)
const makeQueryString = () => {
    const queryString = clickedCheckList
      .map(({ id, content, sortType }) => {
        return sortType === 'category' || sortType === 'types'
          ? `${sortType}_id=${parseInt(id) + 1}`
          : `${sortType}=${content}`;
      })
      .map((item, idx) => {
        return idx === 0 ? item : '&' + item;
      })
      .join('');

    navigate(`?${queryString}`);
  };
  1. makeQueryString이 실행이 되면 navigate를 통하여 useLocation().search 부분이 바뀌게 되고, 그럼 getCardListData의 의존성 배열내의 상태값이 변하기 때문에 필터된 정보를 새롭게 요청하게 된다!

그러면 다중필터 완성! 💯

위의 코드 아래에 한꺼번에 정리 (관련없는 코드 생략)

const LectureList = () => {
 const { search } = useLocation();
 const navigate = useNavigate();

 const [cardList, setCardList] = useState([]);
 const [clickedCheckList, setClickedCheckList] = useState([]);


 const getCardListData = useCallback(async () => {
   const res = await fetch(
     `${BASE_URL}/main/search${search}`
   );

   const data = await res.json();

   setCardList(data.result);
 }, [search]);

 useEffect(() => {
   getCardListData();
 }, [getCardListData]);

 const makeQueryString = () => {
   const queryString = clickedCheckList
     .map(({ id, content, sortType }) => {
       return sortType === 'category' || sortType === 'types'
         ? `${sortType}_id=${parseInt(id) + 1}`
         : `${sortType}=${content}`;
     })
     .map((item, idx) => {
       return idx === 0 ? item : '&' + item;
     })
     .join('');

   navigate(`?${queryString}`);
 };

 const handleCheckList = (e, content, idx, sort_type) => {
   e.target.checked
     ? setClickedCheckList([
         ...clickedCheckList,
         { id: idx, content, sortType: sort_type },
       ])
     : setClickedCheckList(
         clickedCheckList.filter(list => list.content !== content)
       );
 };
 return (
   <Wrapper>
     <FilterList ref={filterDom}>
       {FILTER_CATEGORYS.map(({ sort_type, title, contents }, idx) => {
         return (
           <Filter key={idx}>
             <Category>
               {title}
             </Category>
             <Contents>
               {contents.map((content, idx) => (
                 <Content
                   key={idx}
                   onClick={e => handleCheckList(e, content, idx, sort_type)}
                 >
                   <input type="checkbox" />
                   {content}
                 </Content>
               ))}

               <Btns>
                 <Button
                   onClick={makeQueryString}
                 >
                   필터 적용
                 </Button>
               </Btns>
             </Contents>
           </Filter>
         );
       })}
     </FilterList>


   </Wrapper>
 );
};

export default LectureList;
const FILTER_CATEGORYS = [
  {
    sort_type: 'category',
    title: '카테고리',
    contents: [
      '국내',
      '일본',
      '유럽',
      '미국',
      '한식',
      '양식',
      '일식',
      '커피·디저트',
      '드로잉',
      '미술',
      '글쓰기',
      '사진',
      '러닝',
      '피트니스',
      '등산',
      '수영',
    ],
  },
  {
    sort_type: 'types',
    title: '클래스 진행 방식',
    contents: ['오프라인', 'VOD', '전자책'],
  },
  {
    sort_type: 'regions',
    title: '지역',
    contents: [
      '서울',
      '경기',
      '인천',
      '부산',
      '경상',
      '대전',
      '걍원',
      '광주',
      '제주',
    ],
  },
  {
    sort_type: 'schedules',
    title: '일정',
    contents: [
      '월요일',
      '화요일',
      '수요일',
      '목요일',
      '금요일',
      '토요일',
      '일요일',
    ],
  },
];

🍯 마무리

다중필터가 처음에는 괜히 어렵게 느껴져서 손대기조차 두려웠는데 내가 정확히 '어떤 문제'를 해결해야하는지, 어떤 순서로 로직을 짜야할지 처음부터 차근차근 고민하다보니 해결할 수 있었다.

사실 이게 한번 갈아엎은 코드인데, 처음에는 makeQueryString이라는 함수를 만드는 대신에 clickedCheckList를 처음부터 쿼리스트링 형태로 저장했었다. 그런데 생각해보니 checkList내에는 체크리스트 정보만 들어가야하는데, 불필요하게 최종 결과값이 들어가 오히려 코드 가독성이 떨어지고 지저분해보였다. (두가지 관심사가 한 코드 내에 들어감으로써)

어떻게 한 코드 내에 한가지 정보만 넣어줄 수 있을까 엄청 고민 끝에,, 갈아엎었다. 관심사의 분리 어렵지만 항상 고민하고 계속해서 더 좋은 코드 방식을 생각해내보자! 아마 내가 짠 코드보다 훨씬 더 좋은 코드들이 많을 거다..! 다른 방법들도 있는지 더 찾아봐야겠다.

profile
꾸준히 성장하기🦋 https://hyodduru.tistory.com/ 로 블로그 옮겼습니다

0개의 댓글