react-router-dom의 useSearchParams 훅을 이용하여 필터링을 구현합니다.
이미지를 보면 URL의 뒷부분에 형광펜으로 칠해진 ?language=javascript
부분이 있다. ?
로 시작하는 이 부분을 쿼리 스트링이라고 하며, 키=값
의 형태로 나타낸다. 만약 여러 쌍의 쿼리 스트링이 있을 경우 &
로 구분한다. 쿼리 스트링은 서버에 데이터를 보내는 용도로도 사용할 수 있지만, 클라이언트에서 데이터 필터링을 할 때도 유용하다.
프로그래머스 코딩 테스트 페이지를 예로 들면, 쿼리 스트링이 없는 상태에서 언어의 기본값은 C
이다. 유저가 다른 언어를 선택하면 쿼리 스트링의 KEY 부분에 language
가 추가되고 VALUE에는 유저가 선택한 언어(예: javascript
)가 들어간다.
쿼리 스트링을 이용한 필터링이 다른 필터링 방식보다 유용한 이유는 다음과 같다:
특정 카테고리가 선택되면 전체 아이템 리스트에서 조건을 만족하는 아이템만 필터링해서 보여주고 싶다. 구현 순서는 다음과 같다:
나는 카테고리 컴포넌트를 구현하기 위해 카테고리의 이름과 쿼리 스트링으로 추가할 값, 이미지의 경로가 필요했다. 그래서 이 3가지 정보를 상수 배열로 관리했다.
export const CATEGORY_LIST = [
{
parameter: 'all',
name: '전체',
imgSrc: '/all.png',
},
{
parameter: 'product',
name: '상품',
imgSrc: '/product.png',
},
{
parameter: 'category',
name: '카테고리',
imgSrc: '/category.png',
},
{
parameter: 'exhibition',
name: '기획전',
imgSrc: '/exhibition.png',
},
{
parameter: 'brand',
name: '브랜드',
imgSrc: '/brand.png',
},
];
이렇게 배열을 만들었다면 카테고리 목록을 만들 때는 CATEGORY_LIST
를 가져와서 map
메서드를 이용하면 된다.
import Category from '../ui/Category.jsx';
import { CATEGORY_LIST } from '../../helpers/constants.js';
function CategoryList() {
return (
<ul>
{CATEGORY_LIST.map((category) => (
<Category key={category.name} {...category} />
))}
</ul>
);
}
export default CategoryList;
쿼리 스트링을 추가하는 데는 다양한 방법이 있지만, 이번 프로젝트에서는 react-router-dom
의 useSearchParams
훅을 이용했다.
useSearchParams
훅은 useState
와 유사하게 두 가지 값을 리턴하는데, params와 params의 setter 함수가 그것이다.
import { useSearchParams } from 'react-router-dom';
const [searchParams, setSearchParams] = useSearchParams();
searchParams
의 get
과 set
메서드를 통해 각각 쿼리 스트링을 가져오거나 추가할 수 있다.
searchParams.set('key', 'value');
searchParams.get('key') // value
사용 방법을 익혔으니 실제 프로젝트에 적용해보자!
<CategoryList />
컴포넌트에서는 CATEGORY_LIST
의 요소 개수만큼 <Category />
컴포넌트를 반복 출력하고 있다.
<Category />
컴포넌트는 부모로부터 parameter
, name
, imgSrc
값을 받아온다. 이 중 카테고리를 클릭했을 때 parameter
를 쿼리 스트링에 추가하면 된다.
import { useSearchParams } from 'react-router-dom';
function Category({ parameter, name, imgSrc }) {
const [searchParams, setSearchParams] = useSearchParams();
const setSortParams = () => {
searchParams.set('sort', parameter);
setSearchParams(searchParams);
};
return (
<li onClick={setSortParams}>
<img src={imgSrc} alt={name} />
<span>{name}</span>
</li>
);
}
export default Category;
sort
라는 키를 가진 쿼리 스트링의 값이 잘 추가되는 모습을 볼 수 있다.
필터링을 할 때는 useEffect
를 사용하면 된다. 의존성 배열에 sort
를 넣어 sort
값이 바뀔 때마다 아이템의 목록을 필터링하는 방식이다.
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom';
import { useState, useEffect } from 'react';
import Card from './Card';
function ProductList() {
const [searchParams] = useSearchParams();
const sort = searchParams.get('sort'); // sort 값을 가져온다.
const isAll = !sort || sort === 'all';
const { products } = useSelector((state) => state.products);
const [filteredProducts, setFilteredProducts] = useState(products);
useEffect(() => {
if (!products.length) return;
if (isAll) {
// sort의 값이 '전체'일 경우 전체 상품을 렌더링한다.
setFilteredProducts(products);
return;
}
// sort의 값을 이용해 전체 상품에서 조건을 만족하는 새 상품으로 구성된 배열을 만든다.
const newProducts = products.filter((product) => product.type.toLowerCase() === sort);
setFilteredProducts(newProducts);
}, [sort, products]);
return (
<div>
// 필터링된 배열을 렌더링한다.
{filteredProducts.map((product) => (
<Card key={product.id} product={product} />
))}
</div>
);
}
export default ProductList;
리덕스 등 다른 로직이 섞여서 복잡해 보일 수 있지만 원리 자체는 단순하다.
완성된 모습은 아래와 같다!
이제 새로고침을 하거나 주소를 공유해도 같은 화면을 볼 수 있다.