codinghatso

Learn Next.js 제 11장 : Adding Search and Pagination 본문

WEB/Next.js

Learn Next.js 제 11장 : Adding Search and Pagination

hatso 2025. 3. 11. 22:45

-ai를 사용해 개념정리 했음을 공지합니다.-

 

invoices page를 완성하는 챕터입니다. invocies page에는 검색기능과 6개의 고객 정보를 담는 테이블 6개를 넘어서면 다음 page의 데이터를 가져오는 Pagination 기능 총 3가지를 구현합니다.

검색 원리 분석

  • /dashboard/invoices?page=1&query=pending URL쿼리를 객체로 묶어 보면 이렇게 표시할 수 있다.
    • {page: '1', query: 'pending'}.

input값을 query로 받아오는 모습

'use client';

import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
import { useDebouncedCallback } from 'use-debounce';

export default function Search({ placeholder }: { placeholder: string }) {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const { replace } = useRouter();

  const handleSearch = useDebouncedCallback((term) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }, 300);

  return (
    <div className="relative flex flex-1 flex-shrink-0">
      ......
    </div>
  );
}

📌 변수 분석

  1. searchParams = useSearchParams();
    • 현재 URL의 쿼리 파라미터(검색 매개변수) 를 가져오는 훅.
    • searchParams.get("page") → ?page=2 값을 가져올 수 있음.
    • 주로 필터, 페이지네이션 등에 사용됨.
  2. pathname = usePathname();
    • 현재 URL의 경로(path)만 반환하는 훅.
    • 예: https://example.com/dashboard?page=2 → /dashboard 반환.
    • 쿼리 파라미터를 제외한 URL 경로를 알고 싶을 때 사용됨.
  3. replace = useRouter();
    • Next.js의 라우터 객체를 가져오는 훅.
    • replace(url) → 현재 URL을 새 URL로 교체(뒤로 가기 불가).
    • 페이지네이션, 필터 변경 시 페이지를 새로고침 없이 갱신할 때 사용됨.

🚀 즉, 이 세 변수는 URL 경로와 쿼리 파라미터를 조작하여 검색, 필터, 페이지네이션을 구현하는 데 사용됨! 😊


전체적인 흐름은 아래와 같습니다.

  1. 사용자의 입력한 값을 코드로 받아와 활용한다.
    1. function handleSearch(term: string) { ... } 핸들러
    2. onChange={(e) => { handleSearch(e.target.value); }} input 이벤트 감지
  2. searchParams로 URL을 업데이트합니다.
    1. import { useSearchParams } from 'next/navigation'; 라이브러리 사용
    2. const searchParams = useSearchParams(); 라이브러리 변수 선언
    3. function handleSearch(term: string) { const params = new URLSearchParams(searchParams); } 헨들러에서 사용가능하게 선언
    4. if (term) { params.set('query', term); } else { params.delete('query'); } 핸들러에서 쿼리 판별 후 set | delete 사용
    5. const pathname = usePathname(); usePathname에서 pathname 변수 선언
    6.  const { replace } = useRouter(); useRouter에서 replace 변수 선언
    7. replace(`${pathname}?${params.toString()}`); replace를 이용해서 URL 업데이트
    8. 위 코드아래 변수 설명 참조
  3. URL을 입력 필드와 동기화(sync) 상태로 유지합니다.
    1. defaultValue={searchParams.get('query')?.toString()} defaultValue로 URL을 채움 즉 동기화
  4. 검색 쿼리를 반영하도록 테이블을 업데이트합니다.

최종코드

///app/dashboard/invoices/page.tsx
import Pagination from '@/app/ui/invoices/pagination';
import Search from '@/app/ui/search';
import Table from '@/app/ui/invoices/table';
import { CreateInvoice } from '@/app/ui/invoices/buttons';
import { lusitana } from '@/app/ui/fonts';
import { InvoicesTableSkeleton } from '@/app/ui/skeletons';
import { Suspense } from 'react';
import { fetchInvoicesPages } from '@/app/lib/data';

export default async function Page(props: {
  searchParams?: Promise<{
    query?: string;
    page?: string;
  }>;
}) {
  const searchParams = await props.searchParams;
  const query = searchParams?.query || '';
  const currentPage = Number(searchParams?.page) || 1;
  const totalPages = await fetchInvoicesPages(query);
  return (
    <div className="w-full">
      ......
    </div>
  );
}

여기서 짚고 넘어갈 부분은 props로 받아오는 query의 타입을 정의한다는 것이다.

export default async function Page(props: {
  searchParams?: Promise<{
    query?: string;
    page?: string;
  }>;
}){...}

 

Comments