React（TypeScript）× Firebaseでログイン機能を実装した

# 概要

「React（TypeScript）× Firebaseで新規登録機能を実装した」の続編になります。

https://qiita.com/takahashi-yoji/items/a2a97b90a5b5e7cfc224

前回の記事では、新規登録機能の実装を行いましたが今回はログイン機能、アクセス制限を実装しました。
※Firebaseのver9.0で実装しました。

# ログイン機能の追加

ログイン画面のマークアップを修正します。
新規登録する際は、新規登録画面が表示され、ログインする際は、ログイン画面が表示されるような仕様に変更します。
useStateを使って、ログインしているかしていないかの状態を管理します。
```Auth.tsx
const [isLogin, setIsLogin] = useState(true);
```

以下のような`onClik`イベントが発火した際に、useStateの`setIsLogin`を使って状態を変更します。
クリックした際に、状態が反転するような仕様になっています。
`isLogin`が`true`の場合は、ログインしますか？というテキストが表示され、
`isLogin`が`false`の場合は、新規登録しますか？というテキストが表示されます。

```tsx
<span
 className={style.button}
 onClick={() => setIsLogin(!isLogin)}
>
　{isLogin ? "ログインしますか?" : "新規登録しますか？"}
</span>
```

上記のような状態管理を使用し、テキストの出し分けを動的に変更することが可能になります。
`Auth.tsx`の`return`内は、以下のようになります。
```Auth.tsx
return (
    <Fragment>
      <Container>
        <Grid container>
          <Grid item md={4}></Grid>
          <Grid item md={4}>
            <h2>{isLogin ? "新規登録" : "ログイン"}</h2>
            <Box component="form">
              <TextField
                style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
                name="email"
                label="E-mail"
                fullWidth
                variant="outlined"
                value={email}
                onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
                  handleChangeEmail(event);
                }}
              />
              <TextField
                style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
                name="password"
                label="Password"
                fullWidth
                variant="outlined"
                value={password}
                onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
                  handleChangePassword(event);
                }}
              />
              <Button
                fullWidth
                style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
              >
                {isLogin ? "新規登録" : "ログイン"}
              </Button>
              <Grid container>
                <Grid item>
                  <span
                    className={style.button}
                    onClick={() => setIsLogin(!isLogin)}
                  >
                    {isLogin ? "ログインしますか?" : "新規登録しますか？"}
                  </span>
                </Grid>
              </Grid>
            </Box>
          </Grid>
        </Grid>
      </Container>
    </Fragment>
  );
```

次は、実際にログインのロジック部分を作成します。
ログインのロジック部分は、新規登録の際のロジックとメソッドが違うだけでほとんど一緒です。
Firebaseの方で、`signInWithEmailAndPassword`というログイン時に使用するメソッドが準備されているので使用します。

```Auth.tsx
const Login = async () => {
    await signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        console.log(userCredential);
        navigate("/home");
      })
      .catch((error) => {
        alert(error.message);
        console.error(error);
      });
  };

```

上記のロジックを実際のButton部分に組み込みます。
`isLogin`のstateを使用し、ログインする際には、`Login()`が実行されるように、新規登録する際の`Register()`が実行されるように出し分けをします。

```Auth.tsx
<Button
 fullWidth
 style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
 onClick={isLogin ? Register : Login}
>
{isLogin ? "新規登録" : "ログイン"}
</Button>
```

これで、ログインと新規登録の機能の実装が完了しました。
※「パスワードを忘れましたか？」というテキストなんですが、後々パスワードを忘れた際の機能も実装しようと思います。
![React-Redux-App.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628122/fbc68a3f-2618-1157-ef3f-d58447716248.png)

「ログインしますか？」をクリックすると、テキストが変更します。
![スクリーンショット 2022-07-12 9.48.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628122/01875d93-369b-60eb-5bf0-a22d1aa7230c.png)




以上でログイン機能は、実装完了です。
ただ一つ問題があり現在ログインすると、`/home`にリダイレクトされるように設定していますが、http://localhost:3000/home と入力すると、ログイン後のページを閲覧することが可能になっています。
これでは、意味がないのでログインしているユーザーしかページを見れないようにするためのアクセス制限を実装しようと思います。


# アクセス制限

アクセス制限をする前に、`AuthContext.tsx`というファイルを作成します。`react-router-dom`を使用し、ページ遷移を設定していきます。
`react-router-dom`を使うため、`react-router-dom`をインストールします。
```
$ npm install react-router-dom
```

`AuthContext.tsx`で`react-router-dom`を`import`します。
```AuthContext.tsx
import { Routes, Route } from "react-router-dom";
```

`Routes`の中に、`Route`を設定し、このpathのときはこのコンポーネントを表示するような設定をします。
今回は、`/`の場合は`Auth.tsx`を表示されるように設定し、`/home`の場合は`Home.tsx`が表示されるように設定します。

```AuthContext.tsx
<Routes>
 <Route path={`/`} element={<Auth />} />
 <Route path={`/home`} element={<Home />} />
</Routes>
```

以下、全体のコードになります。
```AuthContext.tsx
import React from "react";
import { Routes, Route } from "react-router-dom";
import Auth from "./Auth";
import Home from "./Home";

const AuthContext = () => {
  return (
    <Routes>
      <Route path={`/`} element={<Auth />} />
      <Route path={`/home`} element={<Home />} />
    </Routes>
  );
};

export default AuthContext;
```

これで、ルートの設定は完了です。
次は、本題のアクセス制限の実装になります。
アクセス制限では、まずユーザ-情報があるかないかを判断します。
`App.tsx`にFirebaseと連携し、ユーザ-情報を取得しログインしているかしていないかを判断します。
ユーザー情報の状態管理を行うので、useStateで定義します。またFirebaseに`User`という型があるので、型宣言も行います。
```App.tsx
import type { User } from "firebase/auth";
import { useState } from "react";
~省略~
type UserType = User | null;
~省略~
const [user, setUser] = useState<UserType>(null);
```

`useEffect`を使用し、初回のレンダリングの際にFirebaseへユーザ-情報があるかを確認します。
Firebaseで、`onAuthStateChanged`という現在ログインしているユーザーを管理するメソッドがあるので、`onAuthStateChanged`を使用します。
```App.tsx
import React, { useState, useEffect } from "react"; // useEffectの追加
import { auth } from "./firebase";
~省略~
useEffect(() => {
 const authStateChanged = auth.onAuthStateChanged((user) => {
  setUser(user);
  });
 return () => {
  authStateChanged();
  };
}, []);
```

あとは、useStateの`user`を使って、ログインしてる場合は`AuthContext.tsx`に遷移し、どのコンポーネントへも遷移できるように設定します。
ログインしていない場合は、`Auth.tsx`へ遷移し他ページを閲覧しようとしてもコンポーネントが表示されないように設定します。
```App.tsx
return <>{user ? <AuthContext /> : <Auth />}</>;
```

以上で、アクセス制限の設定が完成です。
```App.tsx
import React, { useState, useEffect } from "react";
import Auth from "./Auth";
import { auth } from "./firebase";
import type { User } from "firebase/auth";
import AuthContext from "./AuthContext";

type UserType = User | null;

const App = () => {
  const [user, setUser] = useState<UserType>(null);

  useEffect(() => {
    const authStateChanged = auth.onAuthStateChanged((user) => {
      setUser(user);
    });
    return () => {
      authStateChanged();
    };
  }, []);

  return <>{user ? <AuthContext /> : <Auth />}</>;
};

export default App;
```

# まとめ
今回は、react-router-domのバージョン6で実装しました。最新のバージョンでの実装方法を記載している記事がなかなかないので、勉強になりました。
次は、投稿機能を実装したいと思います。

# 参考サイト

https://firebase.google.com/docs/auth/web/firebaseui?hl=ja

https://firebase.google.com/docs/auth/web/manage-users?hl=ja

https://reffect.co.jp/react/react-firebase-auth

https://ralacode.com/blog/post/how-to-use-react-router/