はじめに
バックエンドの藤岡です。
今回は前回の「FirebaseとReactによる独立した認証管理(上)」の続きです。前回Firebaseから取得した認証情報とユーザー情報をグローバルステートとしてアプリ内に共有させていく方法を紹介します。
なお、この方法はあくまで私が色々試してきた中で良さそうと感じたものですので、その点予めご了承下さい。
前提知識
- Firebase Authenticationの基礎知識
 - Firestoreの基礎知識
 - Reactの基礎知識
 
前提条件
- Reactアプリの初期化
 - Firebaseの初期化
 - Firebase Authentication、FirestoreがReactアプリ内で使える状態にある
 - React v.18.2.0
 - Firebase v.9.10.0
 - Typescript v.4.8.2
 
本ドキュメントでは、React、Firebaseの基礎的な操作はある程度できることを前提で進めていきます。その点予めご了承下さい。Firebase AuthenticationやFirestoreのセットアップはこちらの公式ドキュメントをご参照下さい。

なお、本ドキュメントのsrcディレクトリは以下のようなディレクトリ構成を想定しています。
src
├── App.tsx
│
├── components
│   └── Main.tsx
│
├── contexts
│   └── authContext.tsx
│
├── firebase
│   ├── emulator.ts
│   └── firebase.ts
│
├── function
│   └── validateUserType.ts
│
├── hook
│   ├── useAuthStatus.tsx
│   ├── useInitUser.tsx
│   ├── useObserveUser.tsx
│   └── useObserveUserDoc.tsx
│
├── index.tsx
│
└── logo.svg
おさらい
全体概要
今回の全体の概要は以下のようになっています。なお、紺色はコンポーネント、黄色はフックとなっています。

全体の流れとしては以下の通りです。
- Firebase AuthenticationやFirestoreから認証情報やユーザー情報を取得
 - 取得した情報をグローバルステートに提供
 - グローバルステートを共有するコンポーネントに提供
 
Firebase Authenticationから取得する認証情報やFirestoreから取得するスナップショットはリアルタイムで反映されるため、グローバルステートで取得する情報は常にリアルタイムの情報を反映することができます。
また、グローバルステートの取得をフック化することで、提供されているコンポーネント配下では、任意の場所でグローバルステートを呼び出すことができます。
今回は図の②と③の範囲を紹介します。それでは実際のコードを見ていきます。
グローバルステートを作成する
前回でFirebaseから User | null 型の認証情報と、 DocumentData | null 型のユーザー情報のFirestoreドキュメントを取得できるフックを作成しました。
const { user, userDocData, error } = useInitUser();
次はこのフックの返り値をグローバルステートに入れます。グローバルステートには useContext を使用します。(参考:公式ドキュメント)

useContextの使い方
ここで、軽く useContext の使い方を振り返ります。
まず、全体的な流れとしては以下のようになります。(参考)
const Context = createContext(someContext)でコンテクストを作成します。<Context.Provider value=<value>>でラップした子コンポーネント内で、作成したコンテクストをvalueで呼び出せます。- コンテクストの呼び出しには 
useContextフックを使用します。 

使い方を復習ところで、認証情報を共有するフックを作っていきます。
認証情報を共有するフックを作成する
まず、 createContext で共有するコンテクストを作成します。
const AuthContext = createContext<{
  user: User | null;
  userDocData: DocumentData | null;
  error: any;
}>({
  user: null,
  userDocData: null,
  error: null;
});デフォルト値はすべて null にしておきます。これでコンテクストが作成されました。
次に、 AuthContext コンポーネントがラップした子コンポーネント全体で使えるようなコンポーネントを作成します。
const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const { user, userDocData, error } = useInitUser();
  return (
    <AuthContext.Provider
      value={{
        user,
        userDocData,
        error,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};これで、AuthContext コンポーネントでラップしたコンポーネント全体でコンテクストが使えるようになりました。今回はアプリ全体で使用したいので、 Main コンポーネントをラップしておきます。
// ./src/app.tsx
export function App() {
  return (
      <AuthProvider>
        <Main />
      </AuthProvider>
  );
}最後に、登録したコンポーネントを取得する方法を少し楽にしておきます。 useContext(AuthContext) を引数無しで呼び出せるようにします。こうすることにより、 AuthContext をエクスポートしないで済むので、多少は安全になります。
export const useAuthContext = () => useContext(AuthContext);最後にコード全体は以下のようになります。
// ./src/context/authContext.tsx
import { FC, ReactNode, createContext, useContext } from "react";
import { User } from "firebase/auth";
import useInitUser, { AuthStatus } from "../hook/useInitUser";
import { DocumentData } from "firebase/firestore";
const AuthContext = createContext<{
  user: User | null;
  userDocData: DocumentData | null;
  error: any;
}>({
  user: null,
  userDocData: null,
  error: null;
});
const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const { user, userDocData, error } = useInitUser();
  return (
    <AuthContext.Provider
      value={{
        user,
        userDocData,
        error,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};
export const useAuthContext = () => useContext(AuthContext);
コンテクストを取得するときは以下のように useAuthContext() を呼び出すだけです。
// ./src/components/Main.tsx
  
const Main = () => {
  const { user, userDocData } = useAuthContext();
  
  // ...
}
これで認証情報を自由に呼び出せるようになりました。今回はメインとなるコンポーネントは Main コンポーネントのみでしたが、勿論このMain コンポーネントが階層構造になっていても、下位コンポーネントでこれらを呼び出すことができます。
まとめ
今回はFirebase AuthenticationとFirestoreを使って認証管理をしている場合のReactでの認証管理について紹介させていただきました。今回紹介させていただいた認証管理は、Firebaseのリアルタイムのデータ提供と、Reactの仮想DOMの特徴があってこそのものですので、他のBaaSの認証システムでは機能しない可能性がありますのでご注意下さい。最後に、この記事がなにかの参考になれば幸いです。
                    
