LoginSignup
10
4

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

Posted at

はじめに

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では上手くいきませんでした。

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;

解決策

こちらのTanStack Queryのドキュメントに載っていました。
Providersを作成し、親コンポーネントをProvidersでラップするようにします。

Providers作成に必要なライブラリをインストール

プロジェクトのルートディレクトリに移動し、下記を実行します。

npm install @tanstack/react-query-next-experimental

Providersの作成

ReactQueryStreamedHydrationchildlenをラップし、QueryClientを設定したQueryClientProviderReactQueryStreamedHydrationをラップします。

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です。

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;"を付けるのを忘れずに!

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

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など優秀な翻訳アプリがあるので、是非挑戦してみてください!

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4