<aside> 🌿 リッスン: ユーザーが既にログイン済みかどうかを監視して、ログイン済みならTOPページに、ログアウトしているならサインイン画面を表示させること 前回のセッションが残っている場合に、自動でReduxのStoreに認証情報を追加すること(=自動で再ログインさせること)

</aside>

■目次

1. 認証リッスン関数の作成 auth, firestoreメソッド使用

  1. src > reducks > users > operations.jsを開き、以下のように、listenAuthState関数を作成する

    ※一番上に記述すればOK

    <aside> 🌿 AuthState:認証状態

    </aside>

    ▼コード例 ※  マーカーは追加箇所   マーカーは重要箇所

    import { signInAction } from "./actions";
    import { push } from "redux-first-history";
    import { **auth**, db, FirebaseTimestamp } from "../../firebase/index";
    import {
      **onAuthStateChanged,**
      signInWithEmailAndPassword,
      createUserWithEmailAndPassword,
    } from "firebase/auth";
    import { doc, setDoc, **getDoc** } from "firebase/firestore";
    import {
      isValidEmailFormat,
      isValidRequiredInput,
    } from "../../function/common";
    
    **export const listenAuthState = () => {
      return async (dispatch) => {
        return onAuthStateChanged(auth, (user) => {
    
          if (user) {
            // userがサインインしている(認証されている)場合、データベースからuser情報を取得し、reduxのstoreを更新する
            // 利用可能なプロパティの一覧はdocsを参照
            // <https://firebase.google.com/docs/reference/js/firebase.User>
    
    				// ↓signIn関数と同じことをやっている
            const uid = user.uid;
    
            getDoc(doc(db, "users", uid)).then((snapshot) => {
              const data = snapshot.data();
    
              dispatch(
                signInAction({
                  isSignedIn: true,
                  role: data.role,
                  uid: uid,
                  username: data.username,
                })
              );
            });
    
          } else {
            // userがサインアウトしている(=nullが返された)場合、サインインページを表示
            dispatch(push("/signin"));
          }
        });
      };
    };**
    
    〜〜〜
    
  2. 一旦完了!

2. Authコンポーネントの作成

  1. src > Router.jsxを開き、以下のように認証情報が必要なページ(=ログインしてないと表示できないページ)を<Route path="/" element={<Auth />}></Route>で囲う

    <aside> 🌿 Authコンポーネント内に格納する子Routeコンポーネントのpathは完全一致になるよう記述する

    </aside>

    ▼コード例

    import React from "react";
    import { Route, Routes } from "react-router-dom";
    import { Home, SignIn, SignUp, **Test** } from "./templates";
    **import Auth from "./Auth";**
    
    const Router = () => {
      return (
        <Routes>
          <Route path="signup" element={<SignUp />} />
          <Route path="signin" element={<SignIn />} />
    
    			**<Route path="/" element={<Auth />}>
            <Route index element={<Home />} />
    				<Route path="test" element={<Test />} />** // 動作テスト用、必要なければ削除
          **</Route>**
        </Routes>
      );
    };
    
    export default Router;
    

    ▼参考:Authコンポーネントで囲むときの書き方

    Error: [PrivateRoute] is not a component. All component children of must be a or

  2. Authコンポーネントを作成する

    srcディレクトリ内にAuth.jsxを作成する

    スクリーンショット 2022-10-04 17.41.48.png

  3. Auth.jsxに以下のコードを記述する

    また、src > reducks > users > selectors.jsに(ファイルがなければ作成)、stateのisSignedIntruefalseか(サインインしているか)を確認するための関数getIsSignedIn関数を作成する

    ▼Auth.jsx

    /**
    * // eslint-disable-next-line react-hooks/exhaustive-deps をつけて、
    * useEffectの依存関係に関するエラーを無効にする(今回は初回レンダリング後のみ、
    * useEffect内の関数を走らせたいため)
    * 参考:<https://zenn.dev/mackay/articles/1e8fcce329336d>
    */
    import React from "react";
    import { useEffect } from "react";
    import { useDispatch, useSelector } from "react-redux";
    import { listenAuthState } from "./reducks/users/operations";
    import { getIsSignedIn } from "./reducks/users/selectors";
    import { Outlet } from "react-router-dom";
    
    const Auth = () => {
      const dispatch = useDispatch();
      const selector = useSelector((state) => state);
      const isSignedIn = getIsSignedIn(selector);
    
      useEffect(() => {
        if (!isSignedIn) {
          dispatch(listenAuthState());
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, []);
    
      if (!isSignedIn) {
        return <></>;
      } else {
        return <Outlet />;
      }
    };
    
    export default Auth;
    

    ▼selectors.js

    import { createSelector } from "reselect";
    
    const usersSelector = (state) => state.users; // (state):Store全体で管理しているstate
    
    export const getIsSignedIn = createSelector(
      [usersSelector],
      (state) => state.isSignedIn
    );
    
  4. npm start を実行し、サインイン画面が表示されるか確認する

    また、ログイン情報を入力→サインインボタンクリックで、Home画面が表示されるかも併せて確認する

    <aside> 🌿 ※ 事前にログインしていて且つ、ログインしてから間が空いてないと、isSignedIntrue のままなので、最初からHome画面が表示されるかも・・・

    その場合は検証ツールでコンソールエラーが出ていないか確認、何もなければ次章を行ってから再度こちらを確認すると確実!

    </aside>

3. サインアウト機能の作成 authメソッド使用

  1. src > reducks > users > actions.jsを開き、以下のようにsignOutAction関数を作成する

    ※ マーカーが追加した箇所

    export const SIGN_IN = "SIGN_IN"; // このActionがどんなaction typeかをreducersでは認識しておきたい。だからここでexportして、reducersでimportできるようにしている。
    export const signInAction = (userState) => {
      // ↑ユーザーのアクションで呼び出されるようexportしている
      return {
        type: "SIGN_IN", // 冒頭に記述した定数の値と同じものを記述
        payload: {
          isSignedIn: true,
          role: userState.role,
          uid: userState.uid,
          username: userState.username,
        },
      };
    };
    
    **export const SIGN_OUT = "SIGN_OUT";
    export const signOutAction = () => {
      // サインアウト(ログアウト)ではデータを初期値に戻すだけなので、引数はいらない
      return {
        type: "SIGN_OUT",
        payload: {
          isSignedIn: false,
          role: "",
          uid: "",
          username: "",
        },
      };
    };**
    
  2. src > reducks > users > reducers.jsを開き、以下のようにSIGN_OUTの時のreducersも追加する

    ※ マーカーが追加したところ

    import * as Actions from "./actions";
    import initialState from "../store/initialState";
    
    export const UsersReducer = (state = initialState.users, action) => {
      switch (action.type) {
        case Actions.SIGN_IN:
          return {
            ...state, // 現在のStoreの状態
            ...action.payload, // Actionsから渡されたプレーンなobject これで現在のStoreの状態を上書きする(重複したもののみ)
          };
        **case Actions.SIGN_OUT:
          return {
            ...action.payload,
          };**
        default:
          return state;
      }
    };
    
  3. src > reducks > users > operations.jsを開き、以下のようにsignOut関数を作成する

    ▼コード例

    import { signInAction, **signOutAction** } from "./actions";
    import { push } from "redux-first-history";
    import {
      **auth,**
      db,
      FirebaseTimestamp,
    } from "../../firebase/index";
    import {
      onAuthStateChanged,
      signInWithEmailAndPassword,
      createUserWithEmailAndPassword,
      **signOut**
    } from "firebase/auth";
    
    〜〜〜
    
    **export const isSignOut = () => {** // firebaseのsignOutメソッドと名前が被らないようisSignOutに
      **return async (dispatch) => {
        signOut(auth).then(() => {
          dispatch(signOutAction());
          dispatch(push("/signin"));
        });
      };
    };**
    

    参考:

    Authenticate with Firebase using Password-Based Accounts using Javascript

  4. 動作テスト用に、Home.jsxにサインアウト用のボタン要素を記述する

    ▼コード例

    import React from "react";
    import { **useDispatch**, useSelector } from "react-redux";
    import { getUserId, getUsername } from "../reducks/users/selectors";
    **import { isSignOut } from "../reducks/users/operations";**
    
    const Home = () => {
      **const dispatch = useDispatch();**
      const selector = useSelector((state) => state);
      const uid = getUserId(selector);
      const username = getUsername(selector);
    
      return (
        <>
          <h2>Home</h2>
          <p>ユーザーID:{uid}</p>
          <p>ユーザー名:{username}</p>
          **<button onClick={() => dispatch(isSignOut())}>SIGN OUT</button>**
        </>
      );
    };
    
    export default Home;
    
  5. npm start を実行し、Home画面が表示されたらサインアウトボタンを押して、サインイン画面が表示されるか確認する

    また、サインイン画面の時にアドレスバーをルートドメインだけにして更新しても、サインイン画面が再表示されるか確認する

4. パスワードリセット画面の作成 authメソッド使用

  1. src > templates > signIn.jsxを複製して、Reset.jsxを作成する

    index.jsにもexportしておく

    スクリーンショット 2022-10-04 23.55.04.png

    export { default as Home } from "./Home";
    export { default as Login } from "./Login";
    **export { default as Reset } from "./Reset";**
    export { default as SignIn } from "./SignIn";
    export { default as SignUp } from "./SignUp";
    export { default as Test } from "./Test";
    
  2. const SignInexport default SignIn のSignInを、それぞれResetに書き換える

    ▼コード例

    〜〜〜
    
    const **Reset** = () => {
    
    〜〜〜
    
    };
    
    export default **Reset**;