<aside> 🌿 リッスン: ユーザーが既にログイン済みかどうかを監視して、ログイン済みならTOPページに、ログアウトしているならサインイン画面を表示させること 前回のセッションが残っている場合に、自動でReduxのStoreに認証情報を追加すること(=自動で再ログインさせること)
</aside>
■目次
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"));
}
});
};
};**
〜〜〜
一旦完了!
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
Authコンポーネントを作成する
srcディレクトリ内にAuth.jsxを作成する

Auth.jsxに以下のコードを記述する
また、src > reducks > users > selectors.jsに(ファイルがなければ作成)、stateのisSignedInがtrueかfalseか(サインインしているか)を確認するための関数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
);
npm start を実行し、サインイン画面が表示されるか確認する
また、ログイン情報を入力→サインインボタンクリックで、Home画面が表示されるかも併せて確認する
<aside>
🌿 ※ 事前にログインしていて且つ、ログインしてから間が空いてないと、isSignedIn がtrue のままなので、最初からHome画面が表示されるかも・・・
その場合は検証ツールでコンソールエラーが出ていないか確認、何もなければ次章を行ってから再度こちらを確認すると確実!
</aside>
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: "",
},
};
};**
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;
}
};
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
動作テスト用に、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;
npm start を実行し、Home画面が表示されたらサインアウトボタンを押して、サインイン画面が表示されるか確認する
また、サインイン画面の時にアドレスバーをルートドメインだけにして更新しても、サインイン画面が再表示されるか確認する
src > templates > signIn.jsxを複製して、Reset.jsxを作成する
index.jsにもexportしておく

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";
const SignIn と export default SignIn のSignInを、それぞれResetに書き換える
▼コード例
〜〜〜
const **Reset** = () => {
〜〜〜
};
export default **Reset**;