Gila - 드롭다운 메뉴, 페이지네이션 기능 구현

박상준

2024년 10월 16일

0

Next.js
Gila

드롭다운 메뉴

드롭다운메뉴는 항상 구현하기가 은근 귀찮았다. 상태를 토글해서 드롭다운 메뉴를 렌더링하는 것은 문제가 없지만 클릭했을때의 이벤트 처리가 코드가 많아지기 때문이다. 하지만 다행히 이번에는 shadcn을 사용하기로 했다.

shadcn이 간편하고 개발 속도를 올려준다는 장점이 있지만 제대로 알지 못한 상태에서 사용하면 내 성장에는 도움이 안된다는 생각이 들긴한다. 무작정 사용하지는 말아야겠다.

드롭다운 메뉴에서는 액티비티 필터정보를 넣어서 쿼리로 설정해주는 역할이다.

쿼리 설정

전에 작성했던 카테고리와 비슷하다. 하지만 여기에서는 Link태그를 사용하지 않고 div태그를 사용한 드롭다운이기 때문에 onClick이벤트를 사용해야 한다.

'use client';

export default function SortDropDown() {
  const searchParams = useSearchParams();
  const listSortedValue = searchParams.get('sort');
  const [position, setPosition] = useState(listSortedValue || 'latest');
  const router = useRouter();

드롭다운은 클릭했을때 이벤트를 컨트롤해야하기 때문에 클라이언트 컴포넌트이다. 그리고 searchParams를 사용해 쿼리에 있는 데이터를 가져온다. 여기에서 position은 드롭다운메뉴의 활성화된 상태를 관리한다. 포스트 이미지 이렇게 표시된다. 그래서 sort값이 없으면 최신순으로 설정되도록 state값을 주었다.

  const handleDropdown = (sortvalue: string) => {
    const newSortParams = new URLSearchParams(searchParams.toString());
    if (sortvalue === 'latest') {
      newSortParams.delete('sort');
    } else {
      newSortParams.set('sort', sortvalue);
    }
    router.push(createUrl('/', newSortParams));
  };

이제 드롭다운 메뉴를 클릭했을때 이벤트핸들러이다. 각 드롭다운 메뉴마다 sortValue를 가지고 있어서 클릭하면 파라미터로 sortValue를 전달한다. 그리고 전에 사용했듯이 searchParams을 통해 가져온 쿼리를 가져와 새로운 쿼리를 생성해주고 각 상황에 맞게 쿼리를 설정해준다. 쿼리가 없는 경우에 sort는 최신순이기 때문에 최신순의 value인 latest를 클릭하면 쿼리에서 sort값을 제거하도록 했다. 그 외의 경우에는 sort값을 설정해주고 마지막에 push를 사용해 새로운 쿼리의 경로로 이동한다.

<DropdownMenuRadioItem value="price_asc" onClick={() => handleDropdown('price_asc')}>
  가격 낮은 순
</DropdownMenuRadioItem>

드롭다운 메뉴 코드에서는 이렇게 사용해준다. 포스트 이미지 실제로 잘 동작하는 것을 확인할 수 있다.

페이지네이션

페이지네이션도 shadcn에 있는 컴포넌트를 사용했다. 페이지네이션에서는 여러 버튼들이 존재하기 때문에 조건에 따른 렌더링과 이벤트를 적용하기 까다로웠다.

조건에 따른 렌더링

전체적인 설계는 포스트 이미지 이렇게 양끝에 페이지 이동하는 버튼과 페이지 버튼을 만들었고 각 페이지 이동 버튼, 현재 페이지 이전과 다음 페이지를 제외한 페이지는 생략되도록 설계했다.

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious href={currentPage === 1 ? '' : createUrl('/', prevPageParams)} />
    </PaginationItem>
    {currentPage > 2 && (
      <PaginationItem>
        <PaginationEllipsis />
      </PaginationItem>
    )}
    {currentPage > 1 && (
      <PaginationItem>
        <PaginationLink href={createUrl('/', prevPageParams)}>{prevPage}</PaginationLink>
      </PaginationItem>
    )}
    <PaginationItem>
      <PaginationLink isActive href="#">
        {currentPage}
      </PaginationLink>
    </PaginationItem>
    {lastPage !== currentPage && (
      <PaginationItem>
        <PaginationLink href={createUrl('/', nextParams)}>{nextPage}</PaginationLink>
      </PaginationItem>
    )}
    {lastPage - 1 > currentPage && (
      <PaginationItem>
        <PaginationEllipsis />
      </PaginationItem>
    )}
    <PaginationItem>
      <PaginationNext href={currentPage === lastPage ? '' : createUrl('/', nextParams)} />
    </PaginationItem>
  </PaginationContent>
</Pagination>

우선 가운데 있는 현재 페이지 버튼은 무조건 생성되도록 했다. 그리고 다음 페이지 버튼은 마지막페이지와 현재페이지가 동일할때 사라지고, 이전 페이지는 현재 페이지가 1일때 사라진다. 왼쪽 생략 기호는 현재 페이지가 2이상일때 렌더링되고 오른쪽 생략 기호는 마지막 페이지 이전 페이지보다 작을때 생긴다.

데이터 처리

export default function ActivityPagination({ totalCount, pageItemCount }: Props) {
  const searchParams = useSearchParams();
  const currentPage = Number(searchParams.get('page')) || 1;
  const prevPage = currentPage - 1;
  const nextPage = currentPage + 1;
  const lastPage = Math.ceil(totalCount / pageItemCount);
  const prevPageParams = new URLSearchParams(searchParams.toString());
  prevPageParams.set('page', String(prevPage));
  const nextParams = new URLSearchParams(searchParams.toString());
  nextParams.set('page', String(nextPage));

먼저 container컴포넌트에서 리스폰스를 통해 받는 totalCount값과 한 페이지에 몇개의 아이템이 담겨있는지 size값을 받게된다. 그리고 현재 페이지 값은 만약 쿼리에 page가 있으면 그 값으로 설정하고 없으면 1로 설정한다. 현재 페이지를 기준으로 이전과 다음 페이지의 값을 만들어주고 마지막 페이지값은 totalCount와 size값으로 만들어준다. 나눈값이 소수점일때를 대비해 올림 연산을 추가해줬다.

이제 페이지를 이동할 쿼리를 생성해준다. 이전 페이지는 prevPage의 값을 사용하고 다음 페이지는 nextPage의 값을 사용해준다. 이렇게 각각 생성한 쿼리값을 페이지네이션 버튼의 href로 넣어준다.

{currentPage > 1 && (
  <PaginationItem>
    <PaginationLink href={createUrl('/', prevPageParams)}>{prevPage}</PaginationLink>
  </PaginationItem>
)}

페이지네이션 버튼을 누르면 해당 페이지 쿼리로 이동한다. 포스트 이미지

마무리

이렇게 일단 searchParams를 사용하는 방법과 직접 쿼리를 추가하고 삭제하는 것을 해봤다. 처음이라 아직 제대로 사용하는 것인지 애매하지만 우선 동작은 잘된다! 물론 모든 상황에서 문제없이 동작하는지, 리펙토링할 내용이 있는지 확인해야한다. 아마 다른 팀원분들의 코드를 합치다보면 문제가 발견되지 않을까 싶다.

다음에는 메인 페이지를 합치고 또다시 내가 어떤 역할을 맡게 되는지 포스팅해보겠다!

개의 댓글