Learn Next.js_CH11.Adding Search and Pagination

HyeKong·2024년 6월 16일

Learn Next.js

목록 보기
10/14

https://nextjs.org/learn/dashboard-app/adding-search-and-pagination

이전 챕터에서, 스트리밍으로 대시보드의 초기 로딩 성능을 향상시켰다.
이제 /invoices 페이지로 넘어가서 서치와 페이지네이션 추가를 알아보자!

In this chapter...

Next.js API : earchParams, usePathname, and useRouter 사용 방법
URL 서치 params를 통해 서치와 페이지네이션 구현

Starting code

/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 될 것이다.

Why use URL search params?

search state 관리를 위해
이 패턴은 클라이언트 사이트 state에서 작업해왔으면 새로울 것.
사용 시 이점

  • 북마크, 공유 가능 url :url에 서치 param이 있기 때문에, 사용자는 서치 쿼리, 필터를 포함해 앱의 현 상태를 북마크할 수 있다.
  • 서버 사이드 렌더링과 초기 로드 : url params는 초기 state 렌더를 위해 서버에서 직접 소비된다. 이것은 서버 렌더링을 다루는 것을 쉽게 한다.
  • 분석, 트래킹 : url에서 서치 쿼리와 필터를 갖는 것은 추가적 클라이언트 사이드 로직 없이 사용자의 행동 추적을 쉽게한다.

Adding the search functionality

서치 기능 구현을 위해 필요한 넥스트 js 클라이언트 훅

  • useSearchParams : 현 url의 파라미터에 접근하게 함.
    예시로, /dashboard/invoices?page=1&query=pending 은 다음과 같이 될 것이다.
    {page: '1', query: 'pending'}
  • usePathname: url의 경로 이름을 읽게 함.
    예시로, /dashboard/invoices은 /dashboard/invoices를 반환한다.
  • useRouter : 클라이언트 컴포넌트 안에서 루트 간 항해를 프로그래밍적으로 하게 함

구현 단계
1. 사용자의 인풋 캡처
2. 서치 파람으로 url 갱신
3. 인풋 필드와 함께 url 동기화
4. 서치 쿼리 반영 위해 테이블 업데이트

1. Capture the user's input

Search 컴포넌트로 간다. (/app/ui/search.tsx)

  • use client : 이벤트 리스너, 훅 사용 가능한 클라이언트 컴포넌트임을 의미
  • input : 서치 인풋

새로운 handleSearch 기능을 생성하고, 인풋 요소에 onChange 를 추가한다.
onChange는 인풋 값 변경 시마다 handleSearch를 호출할 것

동작 잘 하나 보고 서치 필드에도 타이핑 해보면 콘솔에 찍히는 것을 볼 수 있다.
이것이 사용자 서치 인풋 캡처이다.
이제, 서치 term으로 url을 갱신해야 한다.

2. Update the URL with the search params

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 메소드를 사용한다.

다음 일이 일어난다.

  • ${pathname}은 현재 경로이다. : /dashboard/invoices
  • 사용자가 서치 바에 타이핑 시, params.toString()이 이 인풋을 url 친화적 형식으로 바꾼다.
  • replace(pathname?{pathname}?{params.toString()}) 는 사용자의 서치 데이터로 url을 업데이트 한다. 예시로, 사용자가 lee를 서치하면 /dashboard/invoices?query=lee가 된다.
  • url은 페이지 리로딩 없이 갱신된다. Next.js의 클라이언트 사이드 네비게이션 덕에

3. Keeping the URL and input in sync

인풋 필드와 url 동기화를 위해 인풋에 searchPArams에서 읽어온 defaultvalue를 전달할 수 있다.

defaultValue vs. value / Controlled vs. Uncontrolled

  • 인풋의 calue 관리 시, 컨트롤 된 컴포넌트로 만들기 위해 value 를 사용함 == 리액트가 인풋 상태를 관리함
  • 그러나, state를 사용하지 않고 있기 때문에 defaultValue를 쓸 수 있다 == 원래 인풋은 고유 state를 관리할 것. state 대신 서치 쿼리를 url 에 저장해도 괜찮음

4. Updating the table

마침내 서치 쿼리를 반영한 테이블 컴포넌트를 갱신할 수 있다.
invoices 페이지로 돌아간다.
페이지 컴포넌트는 searchParams라는 prop을 받는다. 그러므로 테이블 컴포넌트에 현재 url params를 전달할 수 있다.

테이블 컴포넌트로 돌아가면 두개의 prop을 볼 것이다 : query, currentPage가, 쿼리에 대응하는 invoices를 반환하는 fetchFilteredInvoices() 함수에 전달되는 것을

term 검색 시, url이 업데이트 된다. => 서버에 새 요청을 보내고, 데이터가 서버에서 fetch되고,쿼리에 맞는 인보이스가 반환될 것을 알수 있다.

When to use the useSearchParams() hook vs. the searchParams prop?
  • 서치 파람의 두 가지 추출 방법. 클라이언트 혹은 서버에서 작동한다.
  • Search : 클라이언트 컴포넌트, 클라이언트 params에 접근을 위해 useSearchParams( ) 훅을 쓴다.
  • Table: 데이터를 가져오는 서버 컴포넌트. 그러므로 페이지 => 컴포넌트로 searchParams prop을 전달 가능
    일반적으로, 클라이언트의 params를 읽어오고 싶을 때, useSearchParams()를 쓴다 => 서버로 돌아가는 것을 방지하기 때문에

Best practice: Debouncing

최적화를 위해, 할 것이 남았다.
handleSearch 함수 안에, 다음 구문 추가

  console.log(`Searching... ${term}`);

그 후 emil 타입 시 콘솔이 이렇다.

모든 키스트로크마다 url을 갱신하고 있기 때문에 그 때마다 eb에 쿼링을 하고 있다.
이것은 문제이다.

Debouncing은 함수가 동작하는 비율을 제한하는 프로그래밍이다.
우리의 경우엔, 사용자가 타이핑을 멈췄을 때만 db에 쿼리를 하고 싶다.

How Debouncing Works:
  1. 이벤트 trigger: 디바운스 되어야 하는 이벤트 발생 시, 타이머 시작
  2. 기다림 : 타이머 만료 전까지 새 이벤트가 발생하면, 타이머가 초기화 된다.
  3. 실행: 타이머가 카운트 다운의 끝에 도달하면, 디바운스 함수가 실행된다.

수동적으로 디바운스 함수를 만드는 것을 포함해 디바운스를 구현할 수 있다.
간단히 하기 위해, use-debounce 라이브러리를 불러온다.

서치 컴포넌트에서, useDebouncedCallback 함수 불러옴

이 함수는 handleSearch의 컨텐츠를 감쌀 것이고. 일정 시간 후에만 코드를 수행한다.(사용자가 타이핑을 멈췄을 시 : 300ms)

서치바에 다시 타이핑하고 콘솔을 열어보면 다음을 볼 수 있다.

디바운싱을 통해, db에 요청을 줄이고, 리소스를 줄일 수 있다.

Adding pagination

테이블이 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을 사용한다.
다음과 같은 일이 일어난다.

  • createPageURL : 현재 서치 파라미터의 객체 생성
  • 제공된 페이지 수에 page 파라미터 업데이터
  • pathname, 업데이트된 서치 파라미터를 사용해 full url 구성 페이지네이션의 나머지 컴포넌트는 다른 state(first, last,active, disabled 등)와 스타일링을 다룬다. 마침내, 사용자가 새 서치 쿼리 타이핑 시, 페이지수를 초기화 하기 원한다.
    => handleSearch 함수에서 할 수 있다.

Summary

클라이언트 스테이트 대신 url 서치 파라미터로 서치와 페이지네이션을 다뤘다.
서버에서 데이터를 페치했다.
더 스무스한 클라이언트 사이드 변환을 위해 useRouter를 썼다.

0개의 댓글