##初めに
今回は、あるアプリケーションのフロントエンドをNext.js,Typescript,Reactを用いて作成していた際に発生したエラーへの対処方法について記載します。
備忘録的な意味合いが強いので、エラーが発生した原因などについては記載いたしません。(私もよくわかっていないため笑)
##発生した問題詳細
useSWRを使用する際、suspenseを用いて値が取得できるまで待つ仕様にした方が何かと便利なため、以下のようなカスタムhooksを使って、useSWRを使用していました。
import useSWR, { BareFetcher, Key, SWRConfiguration } from "swr"
export default function useSuspenseSwr<Data, Error>(
key: Key,
fetcher: BareFetcher<Data> | null,
config: SWRConfiguration<Data, Error, BareFetcher<Data>> = {},
) {
config.suspense = true
const { data, ...rest } = useSWR(key, fetcher, config)
if (data === undefined) {
throw Error("The data of SWR result is undefined. The SWR key is possible falsy.")
}
return { data, ...rest }
}
export const immutableOptions = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
suspense: false,
} as const
これをログインした後にユーザーの情報を子コンポーネントに渡す際に、以下のようなコンポーネントを作成して共通化してました。
'use client'
import { LocalStorageKey } from '@/commons/localStorageKey'
import SwrKey from '@/commons/swrKey'
import useSuspenseSwr, { immutableOptions } from '@/hooks/useSuspenseSwr'
import { getMe, Me } from '@/orval/soloist'
import { createContext, FC, useCallback, useMemo } from 'react'
import { useSWRConfig } from 'swr'
import { AuthenticationProviderProps } from './AuthenticationProvider'
type AuthenticationContextValue = {
user: Me | null
deauthenticate: () => Promise<void>
}
export const AuthenticationContext = createContext<AuthenticationContextValue | undefined>(undefined)
export type AuthenticationProviderPresentationProps = AuthenticationProviderProps & {
getMe: typeof getMe
}
const AuthenticationProviderPresentation: FC<AuthenticationProviderPresentationProps> = ({ children, getMe }) => {
const { data: me } = useSuspenseSwr([SwrKey.USER], () => getMe(), immutableOptions)
const { cache } = useSWRConfig()
const deauthenticate = useCallback(async () => {
localStorage.removeItem(LocalStorageKey.ACCESS_TOKEN)
localStorage.removeItem(LocalStorageKey.REFRESH_TOKEN)
localStorage.removeItem(LocalStorageKey.SWITCH_TENANT_ID)
// キャッシュの削除
;[...cache.keys()].forEach((key) => cache.delete(key))
}, [cache])
const value = useMemo<AuthenticationContextValue>(
() => ({
user: me,
deauthenticate,
}),
[me, deauthenticate],
)
return <AuthenticationContext.Provider value={value}>{children}</AuthenticationContext.Provider>
}
export default AuthenticationProviderPresentation
ただ、suspenseはdefaltDataを指定していないとエラーが発生します。
今回も、設定していなかったため、defaltDataを以下のように設定して再度、dockerでビルドをしなおしたところ、
npm run buildはうまく行きましたが、ハイドレーションエラーが発生して、画面に何も表示されないということが起こりました(涙)
##解決策
先ほどのAuthenticationProviderPresentationで示したコードは、AuthenticationProviderで呼び出して以下のように使用してました。
'use client'
import { getMe } from '@/orval/soloist'
import { FC, PropsWithChildren } from 'react'
import AuthenticationProviderPresentation from ./AuthenticationProviderPresentation
export type AuthenticationProviderProps = PropsWithChildren
const AuthenticationProvider: FC<AuthenticationProviderProps> = (props) => {
return <AuthenticationProviderPresentation {...props} getMe={getMe} />
}
export default AuthenticationProvider
これを、next/dynamicのdynamicを使って以下のように呼び出すように変更しました。
'use client'
import { getMe } from '@/orval/soloist'
import { FC, PropsWithChildren } from 'react'
import dynamic from 'next/dynamic'
export type AuthenticationProviderProps = PropsWithChildren
const AuthenticationProviderPresentation = dynamic(
() => import('@/app/(admin)/dashboard/_components/AuthenticationProviderPresentation'),
{ ssr: false }
)
const AuthenticationProvider: FC<AuthenticationProviderProps> = (props) => {
return <AuthenticationProviderPresentation {...props} getMe={getMe} />
}
export default AuthenticationProvider
これで、suspenseにdefaultDataを設定しなくてもよくなり、ハイドレーションエラーも発生しなくなりました。
理由は謎(笑)
ご存知の方いらっしゃったら教えてくださいませ。。。。