Next.js 13 & TanStack Query(React Query) の注意点

# はじめに
Next.js 13とTanStack Query(React Query)を使ってハマってしまった部分があったので、備忘録として投稿します。
現在(2023/08)は、日本語の記事がまだ少なかったので、情報収集してまとめてみました。
参考になれば嬉しいです！

# 何でハマったか
具体的には、`useQuery`の使用でハマりました。

# 原因
useQueryはClient Componentsでしか使用できませんが、Next.js 13はデフォルトではServer Compenentsになっています。
そのため、useQueryを使用するには、別途設定する必要があります。

通常は下記のように`QueryClient`インスタンスを生成し、`QueryClientProvider`に生成したインスタンスを設定し、`QueryClientProvider`で親コンポーネントをラップするだけで`useQuery`は使用可能ですが、Next.js 13では上手くいきませんでした。
```tsx
import './globals.css'
import type { Metadata } from 'next'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

const client = new QueryClient();

const RootLayout: React.FC<{ children: React.ReactNode }> = ({
  children
}) => {
  return (
    <html lang="en">
      <body>
        <QueryClientProvider client={client}>
          {children}
        </QueryClientProvider>
      </body>
    </html>
  )
}

export default RootLayout;
```

# 解決策
[こちら](https://tanstack.com/query/v5/docs/react/guides/suspense#suspense-on-the-server-with-streaming)のTanStack Queryのドキュメントに載っていました。
`Providers`を作成し、親コンポーネントを`Providers`でラップするようにします。

#### Providers作成に必要なライブラリをインストール
プロジェクトのルートディレクトリに移動し、下記を実行します。
```bash
npm install @tanstack/react-query-next-experimental
```

#### Providersの作成
`ReactQueryStreamedHydration`で`childlen`をラップし、`QueryClient`を設定した`QueryClientProvider`で`ReactQueryStreamedHydration`をラップします。
```tsx:src/providers/index.tsx
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'

export function Providers(props: { children: React.ReactNode }) {
  const [queryClient] = React.useState(() => new QueryClient())

  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>
        {props.children}
      </ReactQueryStreamedHydration>
    </QueryClientProvider>
  )
}
```

#### 親コンポーネントにProvidersを設定
プロジェクト全体で`useQuery`を使用する場合は、ルート(Next.js 13ではlayout.tsx)に`Providers`を設定します。
先ほど作成した`Providers`をimportして`children`をラップするだけでOKです。
```tsx:src/app/layout.tsx
import './globals.css'
import type { Metadata } from 'next'
import Providers from '@/providers'

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

const RootLayout: React.FC<{ children: React.ReactNode }> = ({
  children
}) => {
  return (
    <html lang="en">
      <body>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  )
}

export default RootLayout;
```

#### useQueryを使用して動作確認
これでやっと`useQuery`を使用できるようになりました！
`useQuery`を使用する際は、使用するコンポーネントをClient Componentsとして扱う必要があるため、ファイルの先頭に`"use client;"`を付けるのを忘れずに！
```tsx:src/app/page.tsx
"use client";

import { useQuery } from "@tanstack/react-query";
import PostResponseType from "@/types/postResponseType";
import axios from "../lib/axios";
import Loading from "@/components/Loading";

export const Page: React.FC = () => {

  const getData = async (): Promise<PostResponseType[]> => {
    const res = await axios.get("/posts");
    return res.data ?? [];
  }

  const { data, isLoading, isError } = useQuery<PostResponseType[]>({
    queryKey: ["posts"],
    queryFn: getData
  });

  return (
    <div className="p-4">
      <div className="pb-8 text-5xl text-center">posts</div>
      <div className="pb-8 w-3/5 m-auto">
        {isLoading ? (
          <Loading
            text="データ取得中です。しばらくお待ちください。"
          />
        ) : isError ? (
          <p>Error!</p>
        ) : (
          data?.map((post, index) => (
            <ul key={index} className="pb-8 text-left">
              <li>
                <strong>userId: </strong>
                <span>{post.userId}</span>
              </li>
              <li>
                <strong>id: </strong>
                <span>{post.id}</span>
              </li>
              <li>
                <strong>title: </strong>
                <span>{post.title}</span>
              </li>
              <li>
                <strong>body: </strong>
                <span>{post.body}</span>
              </li>
            </ul>
          ))
        )}
      </div>
    </div>
  )
}

export default Page;
```
ちなみに`"use client;"`を忘れるとこうなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616933/8e3ca85e-65b8-e219-69f6-81c3b368cf10.png)
```
Unhandled Runtime Error
Error: Attempted to call useQuery() from the server but useQuery is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.
```

# おわりに
日本語で情報がなくても、英語で検索することで解決できる場合もあります！
英語ができなくても、[DeepL](https://www.deepl.com/ja/translator)など優秀な翻訳アプリがあるので、是非挑戦してみてください！