Next.js Context API を使用してローディング呼び出し

ツイート
2021年09月11日
2021年09月12日

ドキュメント

課題

Recoilによる、開発体験は非常によかったが、下記のエラーに悩まされた。

bash
app_1 | Duplicate atom key "isLoading". This is a FATAL ERROR in app_1 | production. But it is safe to ignore this warning if it occurred because of app_1 | hot module replacement.

Issueはこちら

[SSR][NextJS] Duplicate atom key during development and during production build in nextjs · Issue #733 · facebookexperimental/Recoil

やりたいこととしては、シンプルなので、React Context APIを使用する。

やりたいこと

  • ローディングコンポーネントの呼び出し
  • メッセージ(アラート)の呼び出し

などの状態をグローバルステート(React Context API)で管理し、開発体験を向上させる。

ポイント

コンテクストは各階層で手動でプロパティを下に渡すことなく、コンポーネントツリー内でデータを渡す方法を提供します。

React Context 実装

context/state.js
import { useState, createContext, useContext } from 'react'; import Loading from '@/components/atoms/loading'; // 👈 使用するローディングコンポーネント // 👇 Contextの作成 const AppContext = createContext({ loading: false, setLoading: (_) => {}, }); // 👇 _app.tsxでコンポーネントツリーの上の方に追加する export const AppWrapper = ({ children }) => { const [loading, setLoading] = useState(false); const value = { loading, setLoading, }; return ( <AppContext.Provider value={value}> <Loading loading={loading} /> {children} </AppContext.Provider> ); }; // 👇 Pageコンポーネントなどで読み込む export const useAppContext = () => useContext(AppContext);
pages/_app.tsx
import React from 'react'; import { AppProps } from 'next/app'; import { AppWrapper } from '@/context/state'; function MyApp({ Component, pageProps }: AppProps) { return ( <> {/* 👇 Context を コンポーネントツリーに追加 */} <AppWrapper> <Component {...pageProps} /> </AppWrapper> </> ); } export default MyApp;

コンポーネント呼び出し

pages/index.tsx
import { useAppContext } from '@/context/state'; export default function Index() { const { setLoading } = useAppContext(); const func = () => { setLoading(true); // 👈 ローディングの表示 setLoading(false); // 👈 ローディングの非表示 } }

⚠️ SWRでデータがfetchできていない場合のローディング

jsx
import { useAppContext } from '@/context/state'; const { setLoading } = useAppContext(); const { data: user, error } = useSWR('/api/user'); if (!user) { setLoading(true) return <></>; } setLoading(false)

上記の実装だと、下記のエラーが発生する。

bash
react-dom.development.js:67 Warning: Cannot update a component (`AppWrapper`) while rendering a different component (`Index`). To locate the bad setState() call inside `Index`,

レンダリングできていない状態で、Index以外のHooksを呼び出すと警告される。(実行はできるが)

💡 回避手段: staticなローディングコンポーネントを返す。(もっといい方法があるかもしれない)

jsx
import StaticLoading from '@/components/atoms/static-loading'; const { data: user, error } = useSWR('/api/user'); if (!user) return <StaticLoading />;