공부 및 일상기록

[React] 중복 선택 가능한 카테고리 필터 만들기 본문

개발/React

[React] 중복 선택 가능한 카테고리 필터 만들기

낚시하고싶어요 2022. 11. 13. 01:25

Side프로젝트로 진행하는 방탈출 관련 웹페이지를 만들고 있다.

오늘은 방탈출 테마들을 여러가지 카테고리를 받아서 해당하는 테마만 필터링 하는 기능을 만들었다.

아직 백엔드쪽에서 서버도 구현되지 않고 API 명세서도 확실하지 않아서 기능만 구현된 상태다.

해당 이미지는 만든 여러가지 필터 카테고리들이다.

여기서 한가지만 선택한다면 쉽게 구현되었겠지만 나는 중복 선택을 가능하도록 만들어야 했다. 

 

이렇게 원하는 장르를 모두 선택할 수 있다.


버튼컴포넌트

먼저 저 버튼들을 만들어줄 컴포넌트를 만들었다.

//버튼 컴포넌트
import { useEffect } from "react";
import styled from "styled-components";

const CategoryBtn = ({ categoryIndex, state, setState }) => {
  useEffect(() => {
    if (state.length === 0) {
      setState(["전체"]);
    }
  }, [state]);

  const categoryHandler = (e) => {
    console.log(isNaN(e.target.value));
    //해당 State에 클릭한 카테고리가 있는지 확인한다. (있다면 클릭한 카테고리를 반환하고, 없다면 undefinded가 됨)
    const isInclude = state.find((element) => element === e.target.value);
    console.log("isInclude", isInclude);

    //만약 클릭한 카테고리가 "전체" 인 경우 state는 "전체" 로 바꾼다.
    if (e.target.value === "전체") {
      setState(["전체"]);

      //만약 isInclude가 값이 있다면 (클릭해제를 하려는것과 같은 의미), state에서 클릭한 카테고리만 제외시킴
    } else if (isInclude) {
      setState(state.filter((element) => element !== e.target.value));

      // state에 "전체"가 있다면 "전체"를 state에서 빼내고 나머지 클릭한 값들을 넣어줌
    } else {
      setState([
        ...state.filter((element) => element !== "전체"),
        e.target.value,
      ]);
    }
  };

  return (
    <>
      {categoryIndex.map((element) => (
        <Btn
          key={element.name}
          type="button"
          onClick={categoryHandler}
          value={element.value}
          backgroundColor={state.find((el) => el === `${element.value}`)}
        >
          {element.name}
        </Btn>
      ))}
    </>
  );
};
export default CategoryBtn;

const Btn = styled.button`
  margin: 3px;
  font-size: 12px;
  color: ${({ backgroundColor }) => (backgroundColor ? "white" : "black")};
  background-color: ${({ backgroundColor }) =>
    backgroundColor ? "#428bca" : "#fff"};
  border: 1px solid #e5e5e5;
  border-radius: 4px;
  outline: none;
  cursor: pointer;

  &:hover {
    color: #333;
    color: ${({ backgroundColor }) => (backgroundColor ? "white" : "black")};
    background-color: ${({ backgroundColor }) =>
      backgroundColor ? "#428bca" : "#e6e6e6"};
    border-color: #adadad;
  }
`;

 

나는 여러가지 카테고리 목록 (categoryIndex)과 해당 목록별 state들을 부모컴포넌트에서 관리하려고 props로 받아서 만드는 방법을 선택했다.

 

**여기서 Btn 컴포넌트에 backgroundColor 프롭스에 왜 백틱을 걸었는지에 대해 짧게 설명하면 setState를 할 때, event.target.value 처럼 온체인지or온클릭 으로 받아온 값들을 넣으면 숫자형이 문자형으로 바뀌는 문제가 있어서 백틱을 걸어서 해당 부분을 숫자가 아닌 문자로 비교하여 비교가 잘 되도록 한것입니다. 

 


부모컴포넌트

그럼 부모컴포넌트는 어떻게 생겼는지 확인해보자.

//버튼의 부모 컴포넌트
import { useState } from "react";
import styled from "styled-components";

//CategoryIndex들이 모여있는 카테고리컴포넌트도 import한다.
import Category from "./Category";
import CategoryBtn from "./CategoryBtn";

const ThemeFilter = () => {
  const [genre, setGenre] = useState(["전체"]);
  const [location, setLocation] = useState(["전체"]);
  const [score, setScore] = useState(["전체"]);
  const [difficulty, setDifficuldy] = useState(["전체"]);
  const [people, setPeople] = useState(["전체"]);

 
  return (
    <Container>
      <FilterWrap>
        <p>지역별</p>

        <CategoryBtn
          categoryIndex={Category.LocationCategory}
          state={location}
          setState={setLocation}
        />

        <p>장르</p>
        <CategoryBtn
          categoryIndex={Category.GenreCategory}
          state={genre}
          setState={setGenre}
        />
        <p>평점</p>

        <CategoryBtn
          categoryIndex={Category.ScoreCategory}
          state={score}
          setState={setScore}
        />

        <p>난이도</p>
        <CategoryBtn
          categoryIndex={Category.DifficultyCategory}
          state={difficulty}
          setState={setDifficuldy}
        />

        <p>예약 가능 인원</p>
        <CategoryBtn
          categoryIndex={Category.PeopleCategory}
          state={people}
          setState={setPeople}
        />
      </FilterWrap>
    </Container>
  );
};
export default ThemeFilter;

const Container = styled.div`
  height: 370px;
  width: 100%;
  display: flex;
  justify-content: center;
  /* align-items: center; */
  background-color: #eee6c4;
`;

const FilterWrap = styled.div`
  height: 100%;
  width: 90%;
  /* cursor: pointer; */
  border: 1px solid red;
`;

각 카테고리 state들을 만들어 두고 return에서 버튼 컴포넌트를 불러와서 props로 해당 카테고리와 state, setState들을 넘겨주었다.

 


 

카테고리

CategoryIndex는 딱히 보고싶은사람은 없겠지만 혹시 저처럼 코드초보인 경우에 자료가 없으면 이해하지 못하시는 분이 계실수도 있기에 첨부하겠습니다.

//각 카테고리를 정리해둔 카테고리컴포넌트
const Category = {
  GenreCategory: [
    {
      name: "전체",
      value: "전체",
    },
    {
      name: "SF/판타지",
      value: "SF/판타지",
    },
    {
      name: "코믹",
      value: "코믹",
    },
    {
      name: "추리",
      value: "추리",
    },
    {
      name: "잠입/범죄",
      value: "잠입/범죄",
    },
    {
      name: "어드벤처/모험",
      value: "어드벤처/모험",
    },
    {
      name: "스릴러",
      value: "스릴러",
    },
    {
      name: "공포",
      value: "공포",
    },
    {
      name: "19금",
      value: "19금",
    },
    {
      name: "미스테리",
      value: "미스테리",
    },
    {
      name: "미션",
      value: "미션",
    },
    {
      name: "로맨스",
      value: "로맨스",
    },
    {
      name: "드라마/감성",
      value: "드라마/감성",
    },
    {
      name: "동화",
      value: "동화",
    },
  ],

  LocationCategory: [
    {
      name: "전체",
      value: "전체",
    },
    {
      name: "강남",
      value: "강남",
    },
    {
      name: "홍대",
      value: "홍대",
    },
    {
      name: "건대",
      value: "건대",
    },
    {
      name: "신촌",
      value: "신촌",
    },
    {
      name: "대학로",
      value: "대학로",
    },
    {
      name: "강북",
      value: "강북",
    },
    {
      name: "신림",
      value: "신림",
    },
    {
      name: "서울(기타)",
      value: "서울(기타)",
    },
  ],

  ScoreCategory: [
    {
      name: "전체",
      value: "전체",
    },
    {
      name: "⭐️",
      value: 1,
    },
    {
      name: "⭐️⭐️",
      value: 2,
    },
    {
      name: "⭐️⭐️⭐️",
      value: 3,
    },
    {
      name: "⭐️⭐️⭐️⭐️",
      value: 4,
    },
    {
      name: "⭐️⭐️⭐️⭐️⭐️",
      value: 5,
    },
  ],

  DifficultyCategory: [
    {
      name: "전체",
      value: "전체",
    },
    {
      name: "🔒",
      value: 1,
    },
    {
      name: "🔒🔒",
      value: 2,
    },
    {
      name: "🔒🔒🔒",
      value: 3,
    },
    {
      name: "🔒🔒🔒🔒",
      value: 4,
    },
    {
      name: "🔒🔒🔒🔒🔒",
      value: 5,
    },
  ],

  PeopleCategory: [
    {
      name: "전체",
      value: "전체",
    },
    {
      name: "1인",
      value: 1,
    },
    {
      name: "2인",
      value: 2,
    },
    {
      name: "3인",
      value: 3,
    },
    {
      name: "4인",
      value: 4,
    },
    {
      name: "5인",
      value: 5,
    },
  ],
};

export default Category;

해당 카테고리용 버튼컴포넌트는 재사용성이 있다고 판단되어 따로 빼서 사용하였고 카테고리들도 단순하지만 너무 긴 정보들을 담고있는것 같아 따로 빼서 사용하였다.