타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js)/TIL

useSearchParams, URLSearchParams, locaton (with 카테고리 동기화)

슈크림 붕어빵 2024. 3. 3. 16:27

수정할 부분: 사진 업로드하기 (필요한 이유에)

 

필요한 이유

 

맨 위 카테고리와 중간의 컨텐츠 부분 두 군데서 카테고리를 설정할 수 있다.

이럴 때, 두 카테고리를 동기화하고, 활성화된 카테고리를 관리하여 활성화된 색을 보여주도록 할 것이다.

react-router-dom에서 제공하는 locationuseSearchParams를 사용할 것이다.

 

1. useSearchParams

    • react-router-dom 패키지에서 제공되는 훅으로, 현재 URL의 쿼리 문자열을 읽고 쓰기 위해 사용
    •   const [searchParams, setSearchParams] = useSearchParams();
       
    • 참고로 url 쿼리 문자열을 읽기만 할 때는 다음도 가능하다
    •  const params = new URLSearchParams(location.search);
  1. URLSearchParams
    • JavaScript의 내장 객체로, URL의 쿼리 문자열을 파싱하거나 조작하는 데 사용
    • 단일 URL의 쿼리 문자열을 처리하는 데 중점 / 리액트 라우터와는 직접적인 관련 X
    • URLSearchParams을 통해 url을 조작하여 setSearchParams 한다면 현재 url을 바꿀 수 있다.
    •   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);
        };

3. location

 

  • url에서 바뀔 때마다 업데이트하는데 사용한다. => location.search가 바뀔때로 알고 할 수 있음
  • useEffect(() => {
        setActive();
      }, [location.search]);
  • 위에서 말한 바와 같이  url 쿼리 문자열을 읽기만 할 때는 다음도 가능하다
 const params = new URLSearchParams(location.search);

 

 

 

 

components/book/booksFilter.tsx => 카테고리 버튼 클릭시 url 변경(handleCategory) / 카테고리 색상 적용

import styled from "styled-components";
import { useCategory } from "../../../hooks/useCategory";
import Button from "../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 newsSearchParams = new URLSearchParams(searchParams);

    if (newsSearchParams.get("news")) {
      newsSearchParams.delete("news");
    } else {
      newsSearchParams.set("news", "true");
    }
    setSearchParams(newsSearchParams);
  };

  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;
  .category {
    display: flex;
    gap: 8px;
  }
`;

export default BooksFilter;

 

 

hooks/useCategory.ts => 카테고리를 받아와 url params를 참고해 active 설정을 해준다.

 

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

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

  const setActive = () => {
    // location.search는 현재 페이지의 URL에서 쿼리 문자열(query string) 부분
    const params = new URLSearchParams(location.search);
    // params의 category_id 받아옴
    if (params.get("category_id")) {
      // 기존 카테고리에서 active설정을 바꿈
      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();
    });
  }, []);

  // location.search는 현재 페이지의 URL에서 쿼리 문자열(query string) 부분
  // []는 새로고침인데 그것과 다른가?
  useEffect(() => {
    setActive();
  }, [location.search]);
  return { category };
};

 

model/category.ts

export interface Category {
  category_id: number | null;
  category_name: string;
  isActive?: boolean;
}