웹 풀스택

[React / ts] Book Store 만들기 - ⑧ 쿼리스트링을 활용한 BooksFilter 구현

kevinmj12 2025. 4. 23. 23:27

쿼리스트링을 활용하여 BooksFilter를 구현하여 카테고리를 선택하였음을 알 수 있도록 해보자.

 

// BooksFilter.tsx

import styled from "styled-components";
import { useCategory } from "../../hooks/useCategory";
import Button from "../common/Button";
import { useSearchParams } from "react-router-dom";

const BooksFilter = () => {
  const { category } = useCategory();
  const [searchParams, setSearchParams] = useSearchParams();

  const handleCategory = (id: number | null) => {
    const newSearchParams = new URLSearchParams(searchParams);
    if (id === null) {
      newSearchParams.delete("category_id");
    } else {
      newSearchParams.set("category_id", id.toString());
    }

    setSearchParams(newSearchParams);
  };

  const handleNews = () => {
    const newSearchParams = new URLSearchParams(searchParams);
    if (newSearchParams.get("news")) {
      newSearchParams.delete("news");
    } else {
      newSearchParams.set("news", "true");
    }
    setSearchParams(newSearchParams);
  };

  return (
    <BooksFilterStyle>
      <div className="category">
        {category.map((item) => (
          <Button
            size="medium"
            scheme={item.isActive ? "primary" : "normal"}
            key={item.category_id}
            onClick={() => {
              handleCategory(item.category_id);
            }}
          >
            {item.category_name}
          </Button>
        ))}
      </div>
      <div className="new">
        <Button
          size="medium"
          scheme={searchParams.get("news") ? "primary" : "normal"}
          onClick={handleNews}
        >
          신간
        </Button>
      </div>
    </BooksFilterStyle>
  );
};

const BooksFilterStyle = styled.div`
  display: flex;
  gap: 24px;
  margin-bottom: 10px;

  .category {
    display: flex;
    gap: 8px;
  }
`;

export default BooksFilter;

 

URLSearchParams는 URL의 쿼리 문자열을 다루기 위한 Web API로 브라우저에서 ?key=value 형식의 URL 파라미터를 읽고, 수정하고, 추가하거나 삭제할 수 있게 해주는 객체이다.

예를 들어 newSearchParams라는 URLSearchParams가 존재할 때 

newSearchParams.set("id", 1)을 실행하면 기존 URL에 id=1이 추가된다.

 

이를 바탕으로 카테고리 버튼이 클릭될 떄 handleCategory를 통해 

category id가 null인 경우에는 newSearchParams.delete()를 통해 category_id를 제거해주었고

category id가 존재하는 경우에는 newSearchParams.set()을 통해 category_id를 추가해주었다.

 

// useCategory.ts

import { useEffect, useState } from "react";
import { Category } from "../models/category.model";
import { fetchCategory } from "../api/category.api";
import { useLocation } from "react-router-dom";

export const useCategory = () => {
  const [category, setCategory] = useState<Category[]>([]);
  const location = useLocation();

  const setActive = () => {
    const params = new URLSearchParams(location.search);
    if (params.get("category_id")) {
      setCategory((prev) => {
        return prev.map((item) => {
          return {
            ...item,
            isActive: item.category_id === Number(params.get("category_id")),
          };
        });
      });
    } else {
      setCategory((prev) => {
        return prev.map((item) => {
          return {
            ...item,
            isActive: false,
          };
        });
      });
    }
  };

  useEffect(() => {
    fetchCategory().then((category) => {
      if (!category) return;

      const categoryWithAll = [
        {
          category_id: null,
          category_name: "전체",
        },
        ...category,
      ];

      setCategory(categoryWithAll);
      setActive();
    });
  }, []);

  useEffect(() => {
    setActive();
  }, [location.search]);

  return { category };
};

선택된 카테고리를 확인할 수 있도록 isActive라는 속성을 추가한 useCategory이다.

useLocation()과 URLSearchParams를 활용하여 현재 URL의 파라미터를 불러오고,

그 파라미터의 category id와 버튼의 category id가 일치하는 경우 isActive를 true로 설정해준다.