Next.js 서버/클라이언트 환경에서 TanStack Query 사용하기
TanStack Query, devtools 설치
yarn add @tanstack/react-query @tanstack/react-query-devtools
QueryProvider 설정
QueryProvider 컴포넌트를 정의하여 앱의 최상위 컴포넌트에 TanStack Query를 제공하는 설정을 한다.
- TanStack Query는 리액트에서 데이터 패칭, 캐싱, 동기화를 도와주는 라이브러리이다.
- devtools는 개발 도구로 현재 데이터의 상태를 웹 브라우저 우측 하단 플로팅 아이콘을 통해 보여주는 라이브러리이다.
// src/utils/QueryProvider.tsx
'use client';
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React from 'react';
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
staleTime: 60 * 1000,
},
},
});
}
// undefined 로 초기값 설정
let browserQueryClient: QueryClient | undefined = undefined;
function getQueryClient() {
if (typeof window === 'undefined') {
// 서버: 항상 새 쿼리 클라이언트 만들기
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
function QueryProvider({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools
initialIsOpen={process.env.NEXT_PUBLIC_RUN_MODE === 'local'}
/>
</QueryClientProvider>
);
}
export default QueryProvider;
- <QueryClientProiver> 파일 이름은 자유 작명
- 본인처럼 utils 폴더에 넣을 거라면 src 레벨에 넣는 것을 권장 - 상관없지만 사이드이펙트가 발생할 수 있음.
- 위 설정을 하게 되면 각기 다른 환경 (브라우저, 서버)에 따라 적절한 QueryClient 인스턴스를 생성하여 클라이언트와 서버 모두에서 동일한 방식으로 쿼리를 처리할 수 있게 합니다.
주요 코드 상세 설명
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
- 설치한 TanStack Query 라이브러리에서 QueryClient와 QueryClientProvider를 가져 온다.
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
staleTime: 60 * 1000,
},
},
});
}
- makeQueryClient 함수는 새로운 QueryClient 인스턴스를 생성하며, 기본 옵션을 설정한다.
- refetchOnWindowFocus : false -> 창이 포커스 될 때 쿼리를 다시 가져오지 않음.
- retry: false -> 쿼리 실패 시 재시도 하지 않음.
- staleTime : 60 * 1000 -> 쿼리 데이터를 1분 동안 신선한 상태로 유지함.
let browserQueryClient: QueryClient | undefined = undefined;
- 클라이언트 측에서 사용할 QueryClient를 저장할 변수.
- 초기값을 undefined로 설정함.
function getQueryClient() {
if (typeof window === 'undefined') {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
- 첫번째 if문 : 서버 환경에서는 항상 새로운 QueryClient 인스턴스를 반환함.
- 두번째 if문 : 클라이언트 환경에서는 브라우저에 QueryClient가 없으면 새로 생성하고, 있으면 기존 것을 사용함.
- getQuertClient 함수는 위 내용으로 구성되어 있기 때문에 서버나 클라이언트, 환경에 따라 알맞은 QueryClient를 반환함.
function QueryProvider({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools
initialIsOpen={process.env.NEXT_PUBLIC_RUN_MODE === 'local'}
/>
</QueryClientProvider>
);
- 위에서 정의한 getQueryClient 함수를 호출해서 QueryClient를 가져옴.
- QueryClientProvider로 감싸서 React Query를 사용 가능한 상태로 만듦.
- ReactQueryDevtools를 포함해서 개발 모드에서 Query의 상태를 쉽게 디버깅 할 수 있게 함.
쿼리 커스텀 훅 작성
데이터 페칭을 위한 커스텀 훅을 작성한다.
/src/hooks/useDonations.ts
import { useQuery } from '@tanstack/react-query';
export const fetchDonations = async () => {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/donations`, {
method: 'GET',
cache: 'no-store',
});
const result = await response.json();
if (response.ok) {
return result.data;
} else {
throw new Error(result.error);
}
};
export const useDonations = () => {
return useQuery({
queryKey: ['donations'],
queryFn: fetchDonations,
});
};
- fetchDonatios 함수는 데이터 페칭 함수이다.
- cache 설정은 별도로 설정하지 않으면 기본적으로 캐싱이 되는 설정이다.
- no-store 옵션은
- ${process.env.NEXT_PUBLIC_API_URL}은
페이지 컴포넌트에서 데이터 dyhydrate 하기
// src/app/라우트/page.tsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query';
import HomePage from "./(provider)/home/page";
import { fetchDonations } from "../../hooks/useDonations";
import { Suspense } from "react";
export default async function Home() {
const queryClient = new QueryClient();
await queryClient.prefetchQuery({
queryKey: ["donations"],
queryFn: fetchDonations,
});
const dehydratedState = dehydrate(queryClient);
return (
<>
<Suspense fallback={<div>Loading...</div>}>
<HydrationBoundary state={dehydratedState}>
<HomePage />
</HydrationBoundary>
</Suspense>
</>
);
}
- QueryClient : 새로운 QueryClient 인스턴스를 생서앟여 서버에서 데이터를 미리 페칭한다.
- prefetchQuery : 서버사이드에서 데이터를 미리 가져와서 클라이언트 사이드에서 사용할 수 있도록 한다. 이를 통해 페이지 로드 시간을 단축하고 사용자 경험을 개선할 수 있다.
- dehydrate : 서버에서 페칭한 데이터를 직렬화하여 클라이언트에 전달한다.
- HydrationBoundary : 클라이언트에서 데이터를 재수화(hydrate)하여 서버에서 미리 페칭된 데이터를 사용할 수 있게 한다.
import { useQuery } from '@tanstack/react-query';
- TanStack Query로 커스텀 훅을 만드려면 이 훅이 필요하다.
export const fetchDonations = async () => {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/donations`, {
method: 'GET',
cache: 'no-store',
});
const result = await response.json();
if (response.ok) {
return result.data;
} else {
throw new Error(result.error);
}
};
- fetchDonations라는 데이터 페칭 함수를 정의한다.
- 이 함수는 Next.js에서 API Route Handler를 구성하고 그 서버로 데이터를 페칭하는 방법이다.
- fetch 다음 소괄호가 본인이 직접 구성한 API 엔드포인트이다.
- baseURL은 .env.local.에 NEXT_PUBLIC_API_URL이라는 상수에 값을 담아두었다.
- 로컬 호스트를 가리키고 있다. NEXT_PUBLIC_API_URL=http://localhost:3000
- 이름이 헷갈리면 NEXT_PUBLIC_BASE_URL 등으로 자유작명 해도 된다.
- cache: 'no-store' 옵션은 브라우저가 이 API 요청을 캐싱하지 않도록 하는 설정. 필요에 따라 변경해도 됨.
- 이 응답에 대해서 결과값인 response를 비동기적으로 json으로 파싱한 뒤 result라는 변수에 담는다.
- 이 응답값에는 데이터의 반환값인 data, 에러 결과인 error 등 여러가지가 담겨 있다. 그러나 보통은 여기서 data와 error 정도를 사용한다.
- response.ok 즉, 응답이 성공적이면 result.data를 반환하도록 한다. result 안에 이미 data가 있지만 매번 점 표기법으로 .data 이렇게 꺼내와야 하니, 호출 구문이 길어져 처음부터 data를 꺼내오는 게 편리하다.
- 그리고 그게 아니라면 에러 상황일 테니, Error를 result.error에 담아 던진다.
export const useDonations = () => {
return useQuery({
queryKey: ['donations'],
queryFn: fetchDonations,
});
};
- 이 부분이 TanStack Query를 작성하는 문법이다.
- fetchDonations 함수로 데이터 페칭하는 것을 'donations'라는 쿼리 키로 지정하여 useDonations 커스텀 훅을 만든다.
- 나중에 다른 컴포넌트에서 이 쿼리를 사용할 때는 const { data, isPending, error } = useDonation(); 이런 식으로 호출만 하면 된다.
- 쿼리 키는 invalidate 할 때 사용한다. (필요한 경우)
최종 컴포넌트에서 데이터 사용
dyhydrate된 데이터를 최정 컴포넌트에서 사용하도록 props로 데이터를 전달한다.
// components/bank/DoanationList.tsx
'use client';
import React from 'react';
const DonationList = ({ donations }: { donations: any }) => {
return (
<div className="grid grid-cols-1 gap-4">
{donations.map((donation: any) => (
<div key={donation.uuid} className="p-4 border rounded shadow">
<h2 className="text-lg font-bold">일련번호: {donation.serielnumbers}</h2>
<p>후원일자: {new Date(donation.datetime).toLocaleDateString()}</p>
<p>후원자명: {donation.name}</p>
<p>후원액: {donation.amounts.toLocaleString()}원</p>
</div>
))}
</div>
);
};
export default DonationList;
'Programing > Next.js' 카테고리의 다른 글
Next.js의 구 라우팅 방법, Pages Router (1) | 2024.08.27 |
---|---|
Next.js를 사용하는 이유 (자유도의 관점, CSR vs pre-rendering) (8) | 2024.08.25 |
Next.js API Route로 supabase 통신하기 (0) | 2024.08.08 |
API Route Handler + Supabase (0) | 2024.08.07 |
detail page 동적 라우팅 기본 패턴 (0) | 2024.08.06 |
댓글