React에서 filter UI 구현하기 (feat: useSearchParams를 hook으로 관리하기)

데브현·2023년 9월 25일
2

프론트엔드 모음집

목록 보기
7/11

필터(filter)는 사용자가 쉽게 데이터를 고를수 있게 도와준다. 필터를 react-query를 활용하여 구현한 예시를 소개해보려고 한다.

내가 생각하는 필터(Filter)

내가 생각하는 필터 UI의 중요한 점은 새로고침 시에도 그 필터가 유지되어야 한다고 생각한다.
예를 들어 '가격','카테고리','물품 타입' 등 여러 종류의 필터들을 선택하고 물건을 선택했는데
뒤로 왔을때 그 필터들이 다 사라져있다면 사용자 입장에서는 정말 싫을 것이고 해당 사이트를 다시 들어오지 않을 가능성이 매우 높다. 그렇기에 필터를 구현할 때는 이부분도 중요하게 생각되어야 할 요소중 하나이다.

React에서 필터 구현하기

위에서 얘기했듯이 필터가 유지되기 위해서는 url에 query를 통해 데이터를 관리해야한다.
url은 새로고침시 데이터를 유지시킬때 매우 유용하게 쓰일 수단 중 하나이므로 이를 활용한다.
react-router-dom에서는 useSearchParams라는 훅을 제공해준다.
이를 활용하면 query에 쉽게 관리해줄 수 있다.

1. useSearchParams써서 query 세팅하기

type FilterProps = { filterA: string, filterB: number }

const FilterList = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const setFilter = ({ filterA, filterB }: FilterProps): void => {
    if (filterA) searchParams.set('filterA', filterA);
    if (filterB) searchParams.set('filterB', filterB.toString());
    setSearchParams(searchParams);
  };

  return (
    <>
      {filterList.map((filter) => (
        <FilterItem key={filter.id} onClickFilter={setFilter}></FilterItem>
      ))}
    </>
  );
};

필터가 이렇게 여러개일 경우 필터를 클릭했을때 선택한 필터에 따라 searchParamsset을 통해 set한뒤 setSearchParams함수로 query에 세팅하면 된다.

추가로 고려해야 할 상황

현재는 filterA를 if문으로만 감싸 falsy한 값만 체크하도록 예시 코드를 작성하였는데 체크하는 로직을 별도로 프로젝트에 맞춰 할 필요가 있다. setSearchParams로 이전 params와 비교해서 세팅하거나 다른 조건문으로 한 적도 있었다.

export function updateSearchParamsIfDifferent(
  key: string,
  value: string | number | undefined,
  searchParams: URLSearchParams,
): void {
  if (typeof value !== 'undefined' && value.toString() !== searchParams.get(key)) {
    searchParams.set(key, value.toString());
  }
}

const setFilter = ({ filterA, filterB }: { filterA }): void => {
   	const beforeSearchParams = searchParams.toString();
    setParamsOnlyDif('filterA',filterA, searchParams); 
    setParamsOnlyDif('filterB',filterB, searchParams); 
    if (updateSearchParams !== searchParams.toString()) setSearchParams(searchParams);
};

'필터 값이 가져온값과 다를때만 세팅' && '필터들의 값이 다를 때만 세팅'
이런식으로 추가적인 로직을 작업할 수도 있다.

2. react-query를 활용해 상품데이터 보여주기

이제 query를 세팅했으니 해당 query의 값들을 api의 params에 넣어 보내줘야 한다.
그래야 필터에 해당되는 값을 가져올 수 있을테니...

function useFetchProduct(params) {
  return useQuery({
    queryKey: ['PRODUCT_LIST', params],
    queryFn: () => fetchProduct(params),
  });
}
const ProductList = () => {
  
  const params = { 
    filterA: searchParams.get('filterA') || 'default', 
    filterB: searchParams.get('filterB') || 'default'
  }
  const { data: productList, isSuccess } = useFetchProduct(params);

  return (
    <>
      {isSuccess && productList.map((product) => (
        <ProductItem key={product.id}></ProductItem>
      ))}
    </>
  );
};

이런식으로 상품을 보여줘야 하는곳에서는 해당 쿼리를 가져와서 쿼리를 react-query의 params로 넣어주면 된다. queryKey에는 params가 할당되어 있으니 필터가 바뀌면 react-query에서 api를 다시 호출해 새로 데이터를 받을 것이다.

3. hooks로 분리하기

그러나 이렇게 필터를 한곳에서만 쓰면 상관 없겠지만 필터를 컴포넌트로 분리하고.. 컴포넌트 안에서 query를 세팅하고 등등 여러곳에 분산되어있으면 관리가 어려우므로 이런것들은 hook으로 관리하는 것이 좋다.

type FilterProps = { filterA: string, filterB: number }

export const useProductListQuery = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const setFilterQuery = ({ filterA, filterB }: FilterProps): void => {
    if (filterA) searchParams.set('filterA', filterA);
    if (filterB) searchParams.set('filterB', filterB.toString());
    setSearchParams(searchParams);
  };
  
  const getFilterQuery = () => {
    return {
      filterA: searchParams.get('filterA') || 'default',
      filterB: searchParams.get('filterB') || 'default',
    };
  };
  
  return { setFilterQuery, getFilterQuery };
};

필터를 set하고 query값을 가져오는 get을 가진 hooks를 하나 만들면 사용처에서는 쉽게 가져다가 쓸 수 있게 된다.

const ProductList = () => {
  const { getFilterQuery } = useProductListQuery()
  const { data: productList, isSuccess } = useFetchProduct(getFilterQuery());
  return (
    <>
      <FilterList></FilterList>
      {isSuccess && productList.map((product) => (
        <ProductItem key={product.id}></ProductItem>
      ))}
    </>
  );
};

const FilterList = () => {
  const { setFilterQuery } = useProductListQuery()

  const setFilter = ({ filterA, filterB }: FilterProps): void => {
	setFilterQuery({ filterA, filterB });
    // 추가적인 작업
  };

  return (
    <>
      {filterList.map((filter) => (
        <FilterItem key={filter.id} onClickFilter={setFilter}></FilterItem>
      ))}
    </>
  );
};

이런식으로 컴포넌트에서는 query 훅을 가져와 쉽고 깔끔하게 set, get을 할 수 있게 된다.


함께 보면 좋은 글
React로 필터 UI 구현하기 (+ URL 동기화)

profile
I am a front-end developer with 4 years of experience who believes that there is nothing I cannot do.

2개의 댓글

comment-user-thumbnail
2023년 10월 2일

첫줄에 도와주낟 오타나셨어요 글 잘보고갑니당

1개의 답글