useContextを用いてローディング時の不要なデータの描画を防ぐ

### 背景
- 認証状態をキャッシュで保持しており、そのデータを基に`useContext`を用いて認証状態を確認
- リロード時に認証状態を確認する前に、認証前のデータが描画されてしまう

### 本記事の目的
- useContextを用いて認証状態が確定するまでローディングする


## 準備
- useContextを用いて、状態の保持のための記述を行う
- firebaseのAuthenticationを用いて認証状態の管理を行う
  (※ firebaseの認証についての詳しい解説はしないので、大枠を掴んでください)

### AuthContextの実装

```AuthContext.ts
import { createContext } from "react";
import firebase from "firebase/auth";

interface AuthContextProps {
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  user: firebase.User | null;
  loading: boolean;
}

export const AuthContext = createContext<AuthContextProps | undefined>(
  undefined
);

```

### AuthProvider.tsxの実装
```AuthProvider.tsx
import { useState, ReactNode, useEffect, useMemo } from "react";
import firebase from "firebase/auth";
import { FirebaseAuth } from "@/auth/auth";
import { AuthContext } from "@/contexts/AuthContext";

interface AuthProviderProps {
  children: ReactNode;
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
  const firebaseAuth = useMemo(() => new FirebaseAuth(), []);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [user, setUser] = useState<firebase.User | null>(null);
  //初期状態ではloadingをtrueに設定
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = firebaseAuth.auth.onAuthStateChanged((user) => {
      if (user) {
        setIsAuthenticated(true);
        setUser(user);
      } else {
        setIsAuthenticated(false);
        setUser(null);
      }
      //認証状態の確認が終了したらローディングをfalseに設定
      setLoading(false);
    });
    return () => unsubscribe();
  }, [firebaseAuth]);

  const login = async (email: string, password: string) => {
    await firebaseAuth.login(email, password);
  };

  const logout = async () => {
    await firebaseAuth.logout();
  };

  return (
    <AuthContext.Provider
      value={{ isAuthenticated, login, logout, user, loading }}
    >
      {children}
    </AuthContext.Provider>
  );
};

```

### useAuth.tsにてAuthContextを使用
- 定義したAuthContextを用いて、useAuthを実装
```useAuth.ts
import { useContext } from "react";
import { AuthContext } from "@/contexts/AuthContext";

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
};
```

### App.tsxにて`AuthProvider`コンポーネントを使用
```App.tsx
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

import { AuthProvider } from "@/contexts/AuthProvider";

import "@/assets/styles/destyle.css";
import Header from "@/components/Header";


function App() {
  return (
    <div className="flex min-h-screen flex-col">
      //AuthProviderを呼び出す
      <AuthProvider>
        <Router>
          <Header />
          <Routes>
          //任意のルートを設定してください
          </Routes>
        </Router>
      </AuthProvider>
    </div>
  );
}
export default App;
```


## 実装

- 処理内容
    1. 認証状態に応じて表示させるボタンを変化させる
    1. 認証状態が確定するまではボタンを描画させない

```Header.tsx
import { Link, useNavigate } from "react-router-dom";

import { useAuth } from "@/contexts/useAuth";
import Button from "@/components/Button";
import { PATHS } from "@/utils/Paths";

const Header = () => {
  const { isAuthenticated, logout, loading } = useAuth();
  const navigate = useNavigate();

  return (
    <header className="sticky top-0 flex h-16 justify-between border-b-2 border-slate-300 bg-white p-3 shadow-md">
      <Link
        to={isAuthenticated ? PATHS.TOP : PATHS.HOME}
        className="flex flex-col justify-center text-xl font-bold"
      >
        economEye👀
      </Link>

      <div className="flex gap-2">
        //ローディング中は描画をしない。
        //終了したら描画する
        {loading ? (
          <></>
        ) : isAuthenticated ? (
          <Button
            label="ログアウト"
            func={() => {
              logout();
              navigate(PATHS.HOME);
            }}
          />
        ) : (
          <>
            <Button
              label="ログイン"
              func={() => {
                navigate(PATHS.LOGIN);
              }}
            />
            <Button
              label="新規登録"
              func={() => {
                navigate(PATHS.SIGNUP);
              }}
            />
          </>
        )}
      </div>
    </header>
  );
};

export default Header;
```

---
### あとがき
loadingを実装するまでは、リロードをするとログイン状態なのにも関わらず、ログインボタンや新規登録ボタンが一時的に描画されていましたが、loadingを実装することで防ぐことが出来ました。今回はHeaderコンポーネントを例に出しましたが、認証状態でコンポーネントが変化するものは全てloadingを用いて処理をすれば、不要なデータを見せないで実装することが出来ます。また、認証状態だけではなく、他の状態管理においても実装したいなと考えているので、もっと機能を追加したいと考えています。(現状はuseContextを認証周りでしか使っていませんが、、)