😄

tanstack query Decoder Error

2023/11/28に公開

概要

next13でtanstack queryを使う時は, prefetchしてくるかinitialDataをいれるかの方法かと思います。今回は存在しないデータのパスのページを見ようとしたとき、そこの内部でprefetchを使っているので以下のエラーが出たという備忘録です

https://legacy.reactjs.org/docs/error-decoder.html/?invariant=419

このエラーは直訳すると、サーバーは、サーバー レンダリング中のエラーが原因で、このsuspense境界を終了できませんでした。クライアントレンダリングに切り替えました。ということらしいです。

条件

  • next: "13.4.8"
  • @tanstack/react-query: "^4.33.0",
    とします.

出現コード

//getQueryClient.ts
export const getQueryClient = cache(() => new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
      retry: false,
    }
  } }))
 
// Container.tsx

type RootContainerProps = { params: { pokemonName: string } }

export const RootContainer = async ({ params }: RootContainerProps)=>{
  await pokemonService.prefetchPokemonByName(params.pokemonName)
  const dehydratedState = dehydrate(getQueryClient())

  return (
    <>
      <Hydrate state={dehydratedState}>
        <SuspenseLoading>
          <PokemonContainer params={params} />
        </SuspenseLoading>
        <SuspenseLoading>
          <PokemonContainer params={params} />
        </SuspenseLoading>
      </Hydrate>
    </>
  )
}

// layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <MyErrorBoundary>
          <QueryClientProviders> 
            {children}
            <ReactQueryDevtools initialIsOpen={false} />
          </QueryClientProviders>
        </MyErrorBoundary>
      </body>
    </html>
  )
}

となっています. RootContainerにはparamsにポケモンの名前がきます.存在しないとAPIにて404のエラーが返ることになっています。しかし、prefetchの場合は「A promise is returned that will either immediately resolve if no fetch is needed or after the query has been executed. It will not return any data or throw any errors.」の公式通り、errorはthrowされません。

サーバでthrowされないとここで囲っているSuspenseが起動し続けてサーバ レンダリングのコンポーネントがエラーを吐くという流れかなと思います。

従って、errorが放置されたままになるとクライアントに切り替わるという感じだと思います。

これを修正するために、prefetchではなくfetchQueryを使います。以下のように書き換えます.

export const RootContainer = async ({ params }: RootContainerProps)=>{
  try {
    await pokemonService.fetchQueryPokemonByName(params.pokemonName)
  } catch (error) {
    if (error instanceof ResponseError) {
      switch (error.getStatusCode) {
      case 404:
        return notFound()
      default:
        return <ErrorComponent statusCode={error.getStatusCode} />
      }
    } else {
      return <ErrorComponent />
    }
  }

  const dehydratedState = dehydrate(getQueryClient())
  return (
    <>
      <Hydrate state={dehydratedState}>
        <SuspenseLoading>
          <PokemonContainer params={params} />
        </SuspenseLoading>
        <SuspenseLoading>
          <PokemonContainer params={{ pokemonName: 'pikachu' }} />
        </SuspenseLoading>
      </Hydrate>
    </>
  )
}

最初にAPI取得時にエラーになるときはnotFoundを渡すことで404のエラーを返すことができます。他のエラーの場合はErrorComponentとして包んで返してあげましょう.

結論

errorの放置はしないようにした方が良い

Discussion