https://nextjs.org/learn/dashboard-app/adding-search-and-pagination
이전 챕터에서, 스트리밍으로 대시보드의 초기 로딩 성능을 향상시켰다.
이제 /invoices 페이지로 넘어가서 서치와 페이지네이션 추가를 알아보자!
Next.js API : earchParams, usePathname, and useRouter 사용 방법
URL 서치 params를 통해 서치와 페이지네이션 구현
/dashboard/invoices/page.tsx vkdlf dksdp ekdma zhem qnxdusjgrl
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';
export default async function Page() {
return (
<div className="w-full">
<div className="flex w-full items-center justify-between">
<h1 className={`${lusitana.className} text-2xl`}>Invoices</h1>
</div>
<div className="mt-4 flex items-center justify-between gap-2 md:mt-8">
<Search placeholder="Search invoices..." />
<CreateInvoice />
</div>
{/* <Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
<Table query={query} currentPage={currentPage} />
</Suspense> */}
<div className="mt-5 flex w-full justify-center">
{/* <Pagination totalPages={totalPages} /> */}
</div>
</div>
);
}
컴포넌트 별 설명
1. Search : 사용자들이 특정 invoices 찾게 만듦
2. Pagination : 사용자들이 invoices의 페이지 간 이동하게 함
3. Table: invoices 보여줌
서치 기능은 클라이언트, 서버로 span 할 것이다.
사용자가 클라이언트에서 invoice를 서치하면, url params는 업데이트 되고, 데이터는 서버에서 fetch 될 것이다.
그리고 테이블이 새 데이터로 서버에서 rerender 될 것이다.
search state 관리를 위해
이 패턴은 클라이언트 사이트 state에서 작업해왔으면 새로울 것.
사용 시 이점
서치 기능 구현을 위해 필요한 넥스트 js 클라이언트 훅
구현 단계
1. 사용자의 인풋 캡처
2. 서치 파람으로 url 갱신
3. 인풋 필드와 함께 url 동기화
4. 서치 쿼리 반영 위해 테이블 업데이트
Search 컴포넌트로 간다. (/app/ui/search.tsx)
새로운 handleSearch 기능을 생성하고, 인풋 요소에 onChange 를 추가한다.
onChange는 인풋 값 변경 시마다 handleSearch를 호출할 것
동작 잘 하나 보고 서치 필드에도 타이핑 해보면 콘솔에 찍히는 것을 볼 수 있다.
이것이 사용자 서치 인풋 캡처이다.
이제, 서치 term으로 url을 갱신해야 한다.
useSearchParams 훅을 next/navigation에서 불러온 후 변수에 할당한다.
handleSearch 안에, searchParams 변수를 통해 새로운 URLSearchParams 객체를 생성한다.
URLSearchParams: url 쿼리 파라미터 조작을 위한 유틸리티 메소드를 제공하는 웹 api
복잡한 스트링 리터럴 생성 대신, ?page=1&query=a를 얻을 수 있다.
다음은, 사용자 인풋 기반 params를 set한다.
인풋이 비어있으면, 삭제하고 싶을 것이다.
이제 쿼리스트링을 얻을 수 있다. url 업데이트를 위해서는 useRouter, usePathname 훅을 사용할 수 있다.
next/navigation에서 useRouter, usePathname을 불러온다.
그리고 handleSearch 안에 useRouter의 replace 메소드를 사용한다.
다음 일이 일어난다.
인풋 필드와 url 동기화를 위해 인풋에 searchPArams에서 읽어온 defaultvalue를 전달할 수 있다.
defaultValue vs. value / Controlled vs. Uncontrolled
마침내 서치 쿼리를 반영한 테이블 컴포넌트를 갱신할 수 있다.
invoices 페이지로 돌아간다.
페이지 컴포넌트는 searchParams라는 prop을 받는다. 그러므로 테이블 컴포넌트에 현재 url params를 전달할 수 있다.
테이블 컴포넌트로 돌아가면 두개의 prop을 볼 것이다 : query, currentPage가, 쿼리에 대응하는 invoices를 반환하는 fetchFilteredInvoices() 함수에 전달되는 것을
term 검색 시, url이 업데이트 된다. => 서버에 새 요청을 보내고, 데이터가 서버에서 fetch되고,쿼리에 맞는 인보이스가 반환될 것을 알수 있다.
최적화를 위해, 할 것이 남았다.
handleSearch 함수 안에, 다음 구문 추가
console.log(`Searching... ${term}`);
그 후 emil 타입 시 콘솔이 이렇다.

모든 키스트로크마다 url을 갱신하고 있기 때문에 그 때마다 eb에 쿼링을 하고 있다.
이것은 문제이다.
Debouncing은 함수가 동작하는 비율을 제한하는 프로그래밍이다.
우리의 경우엔, 사용자가 타이핑을 멈췄을 때만 db에 쿼리를 하고 싶다.
수동적으로 디바운스 함수를 만드는 것을 포함해 디바운스를 구현할 수 있다.
간단히 하기 위해, use-debounce 라이브러리를 불러온다.
서치 컴포넌트에서, useDebouncedCallback 함수 불러옴
이 함수는 handleSearch의 컨텐츠를 감쌀 것이고. 일정 시간 후에만 코드를 수행한다.(사용자가 타이핑을 멈췄을 시 : 300ms)
서치바에 다시 타이핑하고 콘솔을 열어보면 다음을 볼 수 있다.

디바운싱을 통해, db에 요청을 줄이고, 리소스를 줄일 수 있다.
테이블이 6개의 인보이스만 표시하는 것을 알 수 있다.
data.ts의 fetchFilteredInvoices() 함수가 페이지별 6개만 반환하기 때문이다.
페이지네이션 추가는 사용자가 다른 페이지들을 통해 모든 인보이스를 볼 수 있게 한다.
url 파람을 통해 페이지네이션을 구현하는 방법을 보자
Pagination 컴포넌트로 가면 그것이 클라이언트 컴포넌트인 것을 알 수 있다.
데이터베이스 비밀을 노출하고 싶지 않기 때문에 클라이언트에서 data를 페치하고 시피 않다.
대신, 서버에서 데이터를 가져오고 prop으로 컴포넌트에 보낼 수 있다.
/dashboard/invoices/page.tsx에서, fetchInvoicesPages를 불러오고 searchParams에서 query를 argu로서 전달한다.
fetchInvoicesPages는 서치 쿼리 기반 페이지의 총 수를 반환한다.
예시로, 서치 쿼리에 해당하는 결과가 12개 인보이스로 있으면, 각 페이지는 6개의 인보이스를 반환하므로 토탈 페이지 수는 2이다.
이제 페이지네이션 컴포넌트에 totalPages를 prop으로 전달한다.
페이지네이션 컴포넌트로 가서 usePathname과 useSearchParaas 을 불러온다.
이 이것을 현 페이지를 얻고, 새 페이지를 설정하는 데에 쓴다.
이제 페이지네이션 컴포넌트 안에 createPageURL 함수를 생성한다.
서치와 비슷하게, 새 페이지수 지정을 위한 URLSearchParams, url string 생성을 위한 pathName을 사용한다.
다음과 같은 일이 일어난다.
클라이언트 스테이트 대신 url 서치 파라미터로 서치와 페이지네이션을 다뤘다.
서버에서 데이터를 페치했다.
더 스무스한 클라이언트 사이드 변환을 위해 useRouter를 썼다.