타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js)/TIL
useSearchParams, URLSearchParams, locaton (with 카테고리 동기화)
슈크림 붕어빵
2024. 3. 3. 16:27
수정할 부분: 사진 업로드하기 (필요한 이유에)
필요한 이유
맨 위 카테고리와 중간의 컨텐츠 부분 두 군데서 카테고리를 설정할 수 있다.
이럴 때, 두 카테고리를 동기화하고, 활성화된 카테고리를 관리하여 활성화된 색을 보여주도록 할 것이다.
react-router-dom에서 제공하는 location과 useSearchParams를 사용할 것이다.
1. useSearchParams
-
- react-router-dom 패키지에서 제공되는 훅으로, 현재 URL의 쿼리 문자열을 읽고 쓰기 위해 사용
-
const [searchParams, setSearchParams] = useSearchParams();
- 참고로 url 쿼리 문자열을 읽기만 할 때는 다음도 가능하다.
-
const params = new URLSearchParams(location.search);
- 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;
}