Tanstack Query をNext.js(App Router)で導入
Next.js(App Router)にTanstack Queryを導入する方法について色々と調べたのでメモ
導入のモチベーション
Next.jsではServer Component
とClient Component
の境界が明確で、ユーザー操作が必要なブロックの処理はClient Component
に寄せがちになります。
Tanstack Query を利用すると初期データの読み込みはServer Component
で行い変更後の操作はClient Component
に寄せるなどがちょっとだけやりやすくなります。
Client Component
として導入
まずは基本的なTanstack Queryの導入方法を使って導入してみましょう。
App Router向けの導入は後ほど行います。
ベースの設定
まずはClient Component
としてProvider
を作成します。
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
export default function Provider(
{ children } : { children: React.ReactNode }
) {
const queryClient = new QueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
作成したProvider
をlayout.tsx
に導入します。
import Provider from './provider'
export default function RootLayout(
{ children } : { children: React.ReactNode }
) {
return (
<html lang="ja">
<body>
<Provider>
{children}
</Provider>
</body>
</html>
);
}
利用方法
読み込みページでは以下のようにuseQuery
を利用してデータの読み込みを行います。
利用時はuseQuery
というカスタムフックを利用するのでuse client
でClient Component
にする必要があるので注意が必要です。
'use client'
import { useQuery } from '@tanstack/react-query'
export default function Page() {
const { isPending, error, data } = useQuery({
queryKey: ['fetchDate'],
queryFn: () =>
fetch('http://localhost:3000/lazy-api?time=100').then((res) =>
res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.date}</h1>
</div>
)
}
この場合はAPI通信はCSR
時に行われていてSSR
で配信されるHTMLには「Loading...」と記述されています。
Server Component
に移動
データの取得をApp Routerを利用するのであればデータの取得をSSR時のみに行うServer Component
に変更することができます。
まずはapp/provider.tsx
を以下のように変更します。
'use client'
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
})
}
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
if (isServer) {
return makeQueryClient()
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}
export default function Provider(
{ children } : { children: React.ReactNode }
) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
makeQueryClient()
内のstaleTime: 60 * 1000
がポイントでこちらの指定がないとSSR
時とCSR
時と2回通信が発生してしまいます。
SSR
時に通信を行うapp/page.tsx
を以下のように変更します。。
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
import { ClientComponent } from "./ClientComponent";
export async function fetchDate() {
const res = await fetch(
'http://localhost:3000/lazy-api'
)
return await res.json()
}
export default async function Page() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['fetchDate'],
queryFn: fetchDate,
})
const dehydratedState = dehydrate(queryClient)
return (
<main>
<HydrationBoundary state={dehydratedState}>
<ClientComponent />
</HydrationBoundary>
</main>
);
}
fetchDate()
とqueryFn: fetchDate
がポイントでこれでSSR
時に通信を行い通信結果をキャッシュしています。
データを表示するClientComponent
はカスタムフック(useQuery
)を利用するのでuse client
でClient Component
にする必要があります。
"use client"
import { useQueryClient } from '@tanstack/react-query'
export function ClientComponent(){
const queryClient = useQueryClient()
const data:any = queryClient.getQueryData(['fetchDate'])
return (
<div>
<h1>{data.date}</h1>
</div>
)
}
データの取得はpage.tsx
のSSR時に行われているので取得するHTML上には<h1>2024-06-14T18:23:13.798Z</h1>
と表示されて、queryClient.getQueryData()
でprefetchしたキャッシュデータを取得しているのでCSR
時に通信は発生しないです。
Discussion