ログインができたのでログイン認証とログアウト処理を作りたいと思います。
React Firebase入門シリーズ
1-1・ React Firebase入門 初期設定とsignup - masalibの日記
1-2・ 「react-hook-form」を入れてみた - masalibの日記
1-3・ React Firebase入門 signupの通信エラー対応 - masalibの日記
2・ https://masalib.hatenablog.com/entry/2020/11/27/000000
3・React Firebase入門 ログイン認証とログアウト処理 今ここ
ログイン認証
ログインするとダッシュボード(/dashboard)にリダイレクトするのですが ログイン認証をおこなっていないため、ログインしていない人も/dashboardにアクセスできます。 今後、認証のページを作成するためには
ログイン認証
が必要なのでそちらを作ります。
ログイン認証用のルーターの作成の概要
react-router-domというライブラリーを使ってルーティングをおこなっているのですがそのライブラリーには認証(IF文)などができません。
分岐させるルーターを作るというかオーバーライドさせて分岐させます
ソース
import React from "react" import { Route, Redirect } from "react-router-dom" import { useAuth } from "../contexts/AuthContext" export default function AuthFirebaseRoute({ component: Component, ...rest }) { const { currentUser } = useAuth() return ( <Route {...rest} render={props => { return currentUser ? <Component {...props} /> : <Redirect to="/login" /> }} ></Route> ) }
このソースは理解できていません
私もこの部分を完全に理解していません。 サンプルソースで記載されているのです。 お決まりという形でお願いします
React Router: Declarative Routing for React.js
function PrivateRoute({ children, ...rest }) { let auth = useAuth(); return ( <Route {...rest} render={({ location }) => auth.user ? ( children ) : ( <Redirect to={{ pathname: "/login", state: { from: location } }} /> ) } /> ); }
「 { children, ...rest }」部分ですがpropsで渡ってきた部分のchildrenとそのほかの値に分解している。childrenを出力???WHAT?WHY?
さらに< Route >というタグの中にRedirectのコンポーネントを起動している??WHY?・・・
書いていて悲しいのですが本当に理解できていません・・・😢
const myObj = { name: 'John Doe', age: 35, sex: 'M', dob: new Date(1990, 1, 1) }; const { name: Username, ...rest } = myObj console.log(Username); // => John Doe console.log(rest); // => { age: 35, sex: 'M', dob: Mon Jan 01 1990 00:00:00 GMT-0800 (PST) }
これだけも理解できて嬉しいです
兎にも角にもにもcurrentUserがある場合は 渡ってきた内容(props)をそのまま出力する。
ない場合は、ログインがページにリダレクトさせる。
三項演算子が嫌いな人は普通のIFでも動きます。
if (currentUser){ return <Component {...props} /> } else { return <Redirect to="/login" /> } //return currentUser ? <Component {...props} /> : <Redirect to="/login" />
ログアウト処理
signupやLoginと同様にcontextに処理をつくってその関数を共有させるだけです。全文はたぶん見なくても大丈夫かと思う。
クリックすると展開されます(長文なので注意)
import React, { useContext, useState, useEffect } from "react" import { auth } from "../firebase" const AuthContext = React.createContext() export function useAuth() { return useContext(AuthContext) } export function AuthProvider({ children }) { const [currentUser, setCurrentUser] = useState() const [loading, setLoading] = useState(true) function signup(email, password) { return auth.createUserWithEmailAndPassword(email, password) } function login(email, password) { return auth.signInWithEmailAndPassword(email, password) } function logout() { return auth.signOut() } const value = { currentUser, signup, login, logout } useEffect(() => { // Firebase Authのメソッド。ログイン状態が変化すると呼び出される auth.onAuthStateChanged(user => { setCurrentUser(user); setLoading(false) }); }, []); return ( <AuthContext.Provider value={value}> {!loading && children} </AuthContext.Provider> ) }
追加したのはlooutという関数
function logout() { return auth.signOut() }
共通するためのvalueの値を修正
const value = {
currentUser,
signup,
login,
+ logout
}
ダッシュボードにログアウト処理をつける
全文はたぶん見なくても大丈夫かと思う。
クリックすると展開されます(長文なので注意)
import React, { useState } from 'react' import { useAuth } from "../contexts/AuthContext" import { Link, useHistory } from "react-router-dom" import Button from "@material-ui/core/Button"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; const useStyles = makeStyles((theme: Theme) => createStyles({ container: { display: "flex", flexWrap: "wrap", width: 400, margin: `${theme.spacing(0)} auto` }, looutBtn: { marginTop: theme.spacing(2), flexGrow: 1 }, header: { textAlign: "center", background: "#212121", color: "#fff" }, card: { marginTop: theme.spacing(10) } }) ); const Dashboard = () => { const classes = useStyles(); const [error, setError] = useState("") const { currentUser, logout } = useAuth() const history = useHistory() async function handleLogout() { setError("") try { await logout() history.push("/") } catch { setError("Failed to log out") } } return ( <div> Dashboard テスト用のリンク(あとで治す) {error && {error}} <strong>Email:</strong> {currentUser.email} <h2> <Link to="/login">Login</Link> </h2> <h2> <Link to="/signup">signup</Link> </h2> <div> <Button variant="contained" size="large" color="secondary" className={classes.loginBtn} onClick={handleLogout} > Logout </Button> </div> </div> ) } export default Dashboard
ポイントとしてはログアウトのボタンが押されたら
async function handleLogout() { setError("") try { await logout() history.push("/") } catch { setError("Failed to log out") } }
が呼ばれて、contextで共有したlogout処理が実行される。 通信のエラーハンドリングはしていないがあとで入れます。
結果
ログアウト前
ログアウト後
currentUserがNULLになっている所が確認POINTです
感想
簡単にできると思ったログイン認証にハマった。 実際の動きは理解できるんだけど・・・ソースの作りを説明になるとちょっとできない。これは理解していないよね😢
ログアウトはタイムアウト以外のエラーは設定しなくてもいいはず・・・