# TanStack Query

`@lazarv/react-server`は、[TanStack Query](https://tanstack.com/query)（旧React Query）と連携して、サーバーサイドプリフェッチとクライアントサイドハイドレーションによる強力なデータフェッチを提供します。サーバーコンポーネントでクエリをプリフェッチし、クライアントでシームレスにデータをハイドレートすることで、不要な再フェッチを回避できます。

## インストール

TanStack Queryをプロジェクトにインストールします:

```sh
pnpm add @tanstack/react-query
```

## セットアップ

TanStack Queryを使用するには、`QueryClient`を作成し、アプリを`QueryClientProvider`でラップする必要があります。`QueryClientProvider`はReactコンテキストに依存するため、クライアントコンポーネントである必要があります。

サーバーとブラウザの両方の環境を処理するクエリクライアントファクトリを作成します:

```jsx filename="app/get-query-client.jsx"
import {
  defaultShouldDehydrateQuery,
  isServer,
  QueryClient,
} from "@tanstack/react-query";

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // SSRでは、クライアントでの即座の再フェッチを避けるために
        // デフォルトのstaleTimeを0より大きく設定します
        staleTime: 60 * 1000,
      },
      dehydrate: {
        // 保留中のクエリをデハイドレーションに含める
        shouldDehydrateQuery: (query) =>
          defaultShouldDehydrateQuery(query) ||
          query.state.status === "pending",
      },
    },
  });
}

let browserQueryClient = undefined;

export function getQueryClient() {
  if (isServer) {
    // サーバー: 常に新しいクエリクライアントを作成
    return makeQueryClient();
  } else {
    // ブラウザ: まだない場合は新しいクエリクライアントを作成
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}
```

アプリの残りの部分に`QueryClient`を提供するクライアントコンポーネントを作成します:

```jsx filename="app/providers.jsx"
"use client";

import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query";

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  });
}

let browserQueryClient = undefined;

function getQueryClient() {
  if (isServer) {
    return makeQueryClient();
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

export default function Providers({ children }) {
  const queryClient = getQueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}
```

次に、ルートレイアウトで`Providers`コンポーネントでアプリをラップします:

```jsx filename="app/layout.jsx"
import Providers from "./providers";

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head />
      <body suppressHydrationWarning>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

## サーバーサイドプリフェッチ

`@lazarv/react-server`でTanStack Queryを使用する主な利点は、サーバーコンポーネントでデータをプリフェッチし、クライアントでハイドレートできることです。これにより、ローディング状態なしでデータが即座に利用可能になります。

サーバーコンポーネントで、クエリクライアントを使用してデータをプリフェッチし、クライアントコンポーネントを`HydrationBoundary`でラップします:

```jsx filename="app/page.jsx"
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

import { getPosts } from "./get-posts";
import { getQueryClient } from "./get-query-client";
import Posts from "./posts";

export default function PostsPage() {
  const queryClient = getQueryClient();

  queryClient.prefetchQuery({
    queryKey: ["posts"],
    queryFn: getPosts,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Posts />
    </HydrationBoundary>
  );
}
```

## クライアントコンポーネント

クライアントコンポーネントでは、同じクエリキーで`useSuspenseQuery`（または`useQuery`）を使用できます。サーバーでデータがプリフェッチされている場合、ローディング状態なしで即座に利用可能になります:

```jsx filename="app/posts.jsx"
"use client";

import { useSuspenseQuery } from "@tanstack/react-query";
import { getPosts } from "./get-posts";

export default function Posts() {
  const { data } = useSuspenseQuery({
    queryKey: ["posts"],
    queryFn: getPosts,
  });

  return (
    <ul>
      {data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
```

## 同型データフェッチ

サーバーとクライアント間でデータフェッチロジックを共有するために、ランタイム環境を検出する同型データフェッチャーを作成できます:

```js filename="app/get-posts.mjs"
export async function getPosts() {
  if (typeof document === "undefined") {
    // サーバー: データを直接インポート
    const { default: posts } = await import("../data/posts.json");
    return posts;
  } else {
    // クライアント: APIルートからフェッチ
    const res = await fetch("/api/posts");
    return res.json();
  }
}
```

ファイルシステムルーティングを使用したAPIルートと組み合わせることができます:

```jsx filename="app/api/GET.posts.jsx"
import posts from "../../data/posts.json";

export default async function GET() {
  return new Response(JSON.stringify(posts), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
    },
  });
}
```

## ネストされたハイドレーション境界

異なるデータセットをプリフェッチするために、別々のサーバーコンポーネントに`HydrationBoundary`コンポーネントをネストできます。これは複数のデータ依存関係を構成するのに便利です:

```jsx filename="app/comments-server.jsx"
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

import Comments from "./comments";
import { getComments } from "./get-comments";
import { getQueryClient } from "./get-query-client";

export default function CommentsServerComponent() {
  const queryClient = getQueryClient();

  queryClient.prefetchQuery({
    queryKey: ["comments"],
    queryFn: getComments,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Comments />
    </HydrationBoundary>
  );
}
```

> `@lazarv/react-server`でTanStack Queryを使用する完全な例については、[TanStack Query example](https://github.com/lazarv/react-server/tree/main/examples/react-query)を確認してください。