【React開発】処理待機中に画面全体を覆うオシャレなローディング画面を表示させる方法！

# はじめに
React開発において、処理の待機中にローディングスピナーを画面上に表示させる方法はよく知られていると思います。
ただ、ロード中に画面全体を覆うローディング画面を表示する方法はググってもあまり見つからなかったので、ここでその方法をご紹介します！

# 例を見てみよう！
まずは、今個人開発しているアプリで、画面全体をオーバーレイさせないパターンの例を見てみましょう。

- 画面全体のローディング画面を表示しないパターン

![before1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/dffabfeb-e2d5-f285-3ee7-d84214cb2237.gif)

入力項目に値を入れて登録ボタンを押下すると登録するだけの簡易な画面ですが、
これではちょっと味気ないですね…
登録時にボタンをローディング状態にさせる処理は入れているので、一応処理中であることはユーザーには伝わるとは思いますが、せっかくならもうちょっと`オシャンティー（死語）`な感じにしたいですよね…

ではここで、上記の例に処理を加えていい感じになるようにしてみましょう！

# 実装を追加しよう！

では実装を追加していきましょう。
まずは画面全体を覆うローディング画面のコンポーネントを定義します↓。

```tsx:LoadingOverLay.tsx
import { Box } from "@chakra-ui/react";
import { AnimatePresence, motion } from "framer-motion";
import { FC, memo } from "react";
import { DNA } from "react-loader-spinner";

export const LoadingOverLay: FC<{ isLoadingOverlay: boolean }> = memo(({ isLoadingOverlay }) => {
  return (
    <AnimatePresence mode="wait">
      {isLoadingOverlay && (
        <motion.div
          key="loading-overLay"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 0.3 }}
        >
          <Box position="absolute" top="0" left="0" w="100%" h="100vh" zIndex="999" bg="rgba(0,0,0,0.3)">
            <Box position="absolute" top="50%" left="50%" transform="translate(-50%, -50%)">
              <DNA height={200} width={200} />
            </Box>
          </Box>
        </motion.div>
      )}
    </AnimatePresence>
  );
});
```

やってるのは、z-indexで画面の一番上部に薄いグレーのカバーを表示し、その上の画面中央部にスピナーを表示しているという感じです。
コンポーネントに渡された引数「isLoadingOverlay」でローディング画面の表示/非表示を切り替える形ですね。
また、何やらopacityを色々いじってますが、これはframer-motionというライブラリを利用して、ローディング画面をふわっと表示させるアニメーションの設定です。
あと、スピナーは通常のぐるぐる回るよくあるやつじゃなく、react-loader-spinnerというライブラリのDNAとかいうなんか凄そうなスピナーを使っています（`説明が適当すぎる`）。
<br/>

ローディング画面のコンポーネントは↑で一旦OKですが、<font color="red">`ここで作る画面全体を覆うローディング画面はいろんなコンポーネントで使えたら便利そう`</font>なので、ReactのuseContextの機能で表示フラグをアプリ全体で保持させて、どのコンポーネントからでもローディング画面の表示/非表示の切り替えが出来るようにしてみましょう！
Contextを使うための関数を新たに定義します↓。

```tsx:src/context/LoadingProvider.tsx
import { createContext, FC, ReactNode, useState } from "react";

type LoadingContextType = {
  isLoadingOverlay: boolean;
  setIsLoadingOverlay: (value: boolean) => void;
};

export const LoadingContext = createContext({} as LoadingContextType);

export const LoadingProvider: FC<{ children: ReactNode }> = (props) => {
  const { children } = props;

  const [isLoadingOverlay, setIsLoadingOverlay] = useState(false);
  return (
    <LoadingContext.Provider value={{ isLoadingOverlay, setIsLoadingOverlay }}>
        {children}
    </LoadingContext.Provider>
  );
};
```

↑のLoadingProviderはContextを使うためのwrapper関数で、特別なことはしていません(Contextについては<a href="https://qiita.com/curry__30/items/526b45ede95cdbf2b2ee">こちらの記事</a>)。
ここで重要なのは、↓の箇所で、画面全体を覆うローディング画面を表示するフラグ（isLoadingOverlay）と設定用の関数（setIsLoadingOverlay）をコンポーネント全体で使えるようにしていることですね。

```tsx
<LoadingContext.Provider value={{ isLoadingOverlay, setIsLoadingOverlay }}>
```

では、上で作成したwrapper関数とローディング画面を呼び出す部分を追加します↓。

```diff_tsx:App.tsx
import { useContext } from "react";
import { Router } from "./router/Router";
import { LoadingContext } from "./context/LoadingProvider";
import { LoadingOverLay } from "./components/ui/loading/LoadingOverLay";

function App() {
+  // context
+  const { isLoadingOverlay } = useContext(LoadingContext);

  return (
    <>
+     <LoadingOverLay isLoadingOverlay={isLoadingOverlay} />
      <Router />
    </>
  );
}
export default App;
```

ポイントは、ローディング画面はアプリ全体で使用するため、App.tsxで↑の定義をする点です。
また、ローディング画面のコンポーネントは引数に表示フラグを渡す必要があるため、Contextの値をそのまま渡してやります。

さて、ここまで来たら最後にContextで定義した設定用関数を呼び出す処理を追記しましょう。

```diff_tsx:Settings.tsx(中略)
export const Settings: FC = memo(() => {
  const {
    register,
    handleSubmit,
    formState: { errors },
    reset,
  } = useForm<{ token: string }>();

  // state
  const [isRegistering, setIsRegistering] = useState(false);

+  // context
+  const { setIsLoadingOverlay } = useContext(LoadingContext);

  // hooks
  const { displayMessage } = useMessage();

  // 初期処理
  useEffect(() => reset({ token: "" }), [reset]);

  // 登録
  const registQiitaAPIKey: SubmitHandler<{ token: string }> = useCallback(
    async ({ token }) => {
+      setIsLoadingOverlay(true);
      setIsRegistering(true);
      await DB.registQiitaAPIKey(Util.encrypt(token));
      displayMessage({ title: "登録が正常に完了しました。", status: "success" });
      setIsRegistering(false);
+      setIsLoadingOverlay(false);
      reset({ token: "" });
    },
    [setIsLoadingOverlay, displayMessage, reset]
  );
```

最初に見たGif画像の画面のコンポーネントのソースです（長いので中略してます）。
特別なことはしていませんが一応説明すると、useContextでContextの変数を定義し、登録ボタンの押下時にローディングの表示/非表示処理を追加しているという形ですね。

ここまで出来たらいよいよ動作確認してみましょう！

# 動作確認

![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/b53a0f12-36d8-bcea-e281-4a9d8051fbb2.gif)


**<font color="red">これはオシャンティー（断言）</font>**

というわけで、待機時に画面全体を覆うローディング画面を表示させる方法でした！

# おわりに
上記の説明を見ているとまるでスムーズに実装出来たかのような書きぶりですが、結構詰まりながら実装を進めました。
また、framer-motionやらreact-loader-spinnerやらのこともよく分からず、「react ふわっと表示」や、「react スピナー　おしゃれ」でググったら出てきたものをそのまんま使っています。
業務とは違って、個人開発では好きなライブラリをそのまんま使えるので、色々組み合わせてみるのも良い経験になって非常に面白いですね！

# 参考
https://www.framer.com/motion/

https://mhnpd.github.io/react-loader-spinner/docs/components/dna/

https://qiita.com/curry__30/items/526b45ede95cdbf2b2ee

## JISOUのメンバー募集中🔥
プログラミングコーチングJISOUではメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか？
気になる方はぜひHPからライン登録お願いします！👇

https://projisou.jp/