Next.js (React) でログインユーザー情報がなければ /
にリダイレクトするコンポーネントを作っていて、リロード時に state が無くなるので SessionStorage に保存しておいたデータを取ってきてログインを確認するような事をしていたら下記のような Warning が表示されました。
Warning: Cannot update a component (`Componet`) while rendering a different component.
結論: コンポーネントがレンダリング中に別コンポーネントの state を変更しようとすると Warning が発生する。
⚠ 問題のあったコード
/pages/_app.tsx
import { AppProps } from 'next/app'; import { RecoilRoot } from 'recoil'; import { Auth } from '../Component/Auth'; export default function App({ Component, pageProps }: AppProps) { return ( <RecoilRoot> <Auth> <Component {...pageProps} /> </Auth> </RecoilRoot> ); };
/hooks/useUser.tsx
import { useRecoilState } from 'recoil'; const readUser = (): UserInterface | undefined => { const user = sessionStorage.getItem('user'); return user ? (JSON.parse(user) as UserInterface) : undefined; }; interface IuseUser { user: UserInterface; isUser: () => boolean; } export const useUser = (): IuseUser => { const [user, setUserData] = useRecoilState(userState); const isUser = () => { if (user) { return true; } // SessionStorage にデータがあれば user state を更新する const userData = readUser(); if (!userData) { return false; } setUserData(userData); return true; } return { user, isUser } };
/Component/Auth.tsx
import { useEffect, useState, VFC } from 'react'; import { useRouter } from 'next/router'; import { useUser } from '../hooks/useUser'; interface AuthProps { children: React.ReactNode; } export const Auth: VFC<AuthProps> ({ children }) => { const [loading, setLoading] = useState<boolean>(true); const router = useRouter(); const { isUser } = useUser(); useEffect(() => { if (router.isReady) { setLoading(false); } }, [router.isReady]); if (loading) { return <Loading /> } // state にも SessionStorage にもデータがない場合はリダイレクト if (!isUser()) { router.push('/'); // リダイレクト前に children が一瞬表示されてしまわないように null を返す return null; } return <>{children}</>; }
一度ログインして SessionStorage にデータが有る状態でリロードすると Warning が発生します。
→ Warning: Cannot update a component (`Componet`) while rendering a different component.
コンポーネントがレンダリング中に別コンポーネントの state を変更しようとすると Warning が発生する。
レンダー中に
setState
を呼び出すことはサポートされていますが同じコンポーネントに対してのみ可能です。別のコンポーネントのレンダー中にsetState
を呼び出すと、警告が表示されるようになりました。Warning: Cannot update a component from inside the function body of a different component.この警告は、意図しない状態変更によって引き起こされるアプリケーションのバグを見つけるのに役立ちます。レンダーの結果として他のコンポーネントの状態を意図的に変更したいという稀なケースでは、setState
呼び出しをuseEffect
にラップすることができます。
cf. レンダー中のいくつかの更新に関する警告 | React v16.13.0 – React Blog
問題の箇所
/Component/Auth.tsx
export const Auth: VFC<AuthProps> ({ children }) => { // 略 // state にも SessionStorage にもデータがない場合はリダイレクト if (!isUser()) { // <- state の変更を含む hooks がレンダリング前に実行される router.push('/'); return null; } return <>{children}</>; }
コンポーネント直下ではなく、 useEffect 内で呼び出すようにすればOK
/Component/Auth.tsx
export const Auth: VFC<AuthProps> ({ children }) => { const [loading, setLoading] = useState<boolean>(true); const router = useRouter(); const { isUser } = useUser(); useEffect(() => { if (!router.isReady) { return; } // state にも SessionStorage にもデータがない場合はリダイレクト if (!isUser()) { router.push('/'); return; } // use 情報の確認が完了するまで loading のままにする setLoading(false); }, [router.isReady]); return loading? <Loading /> : <>{children}</>; }
そもそも作った hooks があまり良くない気もしますが、これで Warning の発生しないリロードしても user 情報が保持されて、user 情報がなければ /
にリダイレクトするコンポーネントが作れました!
₍ ᐢ. ̫ .ᐢ ₎ ワーイ
所管
Next.js とか recoil とか色々使ってたけど、単純に React Hooks の使い方の問題でした。
React とか Next.js とか個人制作の雰囲気で触ってるから良い方法なのかダメな方法なのか判断ができないので、ちゃんとしたフロントエンジニアのチームでコードレビュー受けたい〜
[参考]
- React v16.13.0 – React Blog
- 【React】Warning: Cannot update a component from inside the function body of a different component. - Qiita
- React Hooks: 子コンポーネントから親の状態をレンダー時に変えたら叱られた ー Warning: Cannot update a component while rendering a different component - Qiita
- Cannot update a component (component1) while rendering a different component (component2). | RyotArchLog