Chapter 04

Auth:ユーザー作成処理(SignUp):ステップ2

masalib
masalib
2020.12.21に更新

SingUpの処理作成

Firebaseのやり取りの部分です。取得した内容を他の画面でも共有したいので
React hooksのContextを使って共有します。
Contextについては以下の記事を参照してください

contextsのフォルダ

$ cd ProjectFolder
$ cd src
$ mkdir contexts

contextsの作成

/src/contexts/AuthContext.js
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)
    }

    const value = {
        currentUser,
        signup
    }

    useEffect(() => {
        // Firebase Authのメソッド。ログイン状態が変化すると呼び出される
        auth.onAuthStateChanged(user => {
          setCurrentUser(user);
        });
      }, []);

    return (
        <AuthContext.Provider value={value}>
          {children}
        </AuthContext.Provider>
    )
} 

contextsの作成の補足

Contextを指定します。

export function useAuth() {
    return useContext(AuthContext)
}

signupするための関数作成しています

function signup(email, password) {
     return auth.createUserWithEmailAndPassword(email, password)
}

currentUser(変数)とsignup(関数)を共有するために1つにまとめています

    const value = {
        currentUser,
        signup
    }

app.jsなどで共有する範囲について指定しています。valueが共有する部分になります

<AuthContext.Provider value={value}>
    {children}
</AuthContext.Provider>

contextsを読み込む

/src/components/App.js
import React from "react";
import Signup from "./Signup"
+ import { AuthProvider } from "../contexts/AuthContext"
function App() {
  return (
    <>
+   <AuthProvider>
      <Signup />
+   </AuthProvider>
    </>
  );
}
export default App;

以下の範囲が共有できる部分です

<AuthProvider>
   <Signup />
</AuthProvider>

これでSignupのコンポーネント内でContextが使えます

SingUpの処理を画面側にいれる

実際にSignUPを起動するように修正します。
緑が追加した部分で、赤の行が削除した部分です

プログラム全文(長文なので注意)
+ import React, { useReducer, useEffect, useState } from "react";
- import React, { useReducer, useEffect } from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardActions from "@material-ui/core/CardActions";
import CardHeader from "@material-ui/core/CardHeader";
import Button from "@material-ui/core/Button";
+ import { useAuth } from "../contexts/AuthContext";

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    container: {
      display: "flex",
      flexWrap: "wrap",
      width: 400,
      margin: `${theme.spacing(0)} auto`
    },
    signupBtn: {
      marginTop: theme.spacing(2),
      flexGrow: 1
    },
    header: {
      textAlign: "center",
      background: "#212121",
      color: "#fff"
    },
    card: {
      marginTop: theme.spacing(10)
    }
  })
);

//state type
type State = {
  email: string,
  password: string,
  passwordconfirm: string,
  isButtonDisabled: boolean,
  helperText: string,
  isError: boolean
};

const initialState: State = {
  email: "",
  password: "",
  passwordconfirm: "",
  isButtonDisabled: true,
  helperText: "",
  isError: false
};

type Action =
  | { type: "setEmail", payload: string }
  | { type: "setPassword", payload: string }
  | { type: "setPasswordConfirm", payload: string }
  | { type: "setIsButtonDisabled", payload: boolean }
  | { type: "signupSuccess", payload: string }
  | { type: "signupFailed", payload: string }
  | { type: "setIsError", payload: boolean };

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "setEmail":
      return {
        ...state,
        email: action.payload
      };
    case "setPassword":
      return {
        ...state,
        password: action.payload
      };
    case "setPasswordConfirm":
      return {
        ...state,
        passwordconfirm: action.payload
      };
    case "setIsButtonDisabled":
      return {
        ...state,
        isButtonDisabled: action.payload
      };
    case "signupSuccess":
      return {
        ...state,
        helperText: action.payload,
        isError: false
      };
    case "signupFailed":
      return {
        ...state,
        helperText: action.payload,
        isError: true
      };
    case "setIsError":
      return {
        ...state,
        isError: action.payload
      };
    default:
      return state;
  }
};

const Signup = () => {
  const classes = useStyles();
  const [state, dispatch] = useReducer(reducer, initialState);
+   const { signup } = useAuth();
+   const [error, setError] = useState("");
+   const [successMessage, setSuccessMessage] = useState("");

  useEffect(() => {
    if (
      state.email.trim() &&
      state.password.trim() &&
      state.passwordconfirm.trim()
    ) {
      dispatch({
        type: "setIsButtonDisabled",
        payload: false
      });
    } else {
      dispatch({
        type: "setIsButtonDisabled",
        payload: true
      });
    }
  }, [state.email, state.password, state.passwordconfirm]);

-   const handleSignup = () => {
-     if (state.email === "abc@email.com" && state.password === "password") {
-       dispatch({
-         type: "signupSuccess",
-         payload: "Signup Successfully"
-       });
-     } else {
-       dispatch({
-         type: "signupFailed",
-         payload: "Incorrect email or password"
-       });
-     }
-   };
+   async function handleSignup(event) {
+     event.preventDefault();
+     try {
+       setError("");
+       setSuccessMessage("");
+       //sing up ボタンの無効化
+       dispatch({
+         type: "setIsButtonDisabled",
+         payload: true
+       });
+       await signup(state.email, state.passwordconfirm);
+       dispatch({
+         type: "signupSuccess",
+         payload: "Signup Successfully"
+       });
+       //sing up ボタンの有効化
+       dispatch({
+         type: "setIsButtonDisabled",
+         payload: false
+       });
+       setSuccessMessage("アカウントの作成に成功しました");
+     } catch (e) {
+       console.log(e);
+       //エラーのメッセージの表示
+       switch (e.code) {
+         case "auth/network-request-failed":
+           setError(
+             "通信がエラーになったのか、またはタイムアウトになりました。通信環境がいい所で再度やり直してください。"
+           );
+           break;
+         case "auth/weak-password": //バリデーションでいかないようにする
+           setError("パスワードが短すぎます。6文字以上を入力してください。");
+           break;
+         case "auth/invalid-email": //バリデーションでいかないようにする
+           setError("メールアドレスが正しくありません");
+           break;
+         case "auth/email-already-in-use":
+           setError(
+             "メールアドレスがすでに使用されています。ログインするか別のメールアドレスで作成してください"
+           );
+           break;
+         case "auth/user-disabled":
+           setError("入力されたメールアドレスは無効(BAN)になっています。");
+           break;
+         default:
+           //想定外
+           setError(
+             "アカウントの作成に失敗しました。通信環境がいい所で再度やり直してください。"
+           );
+       }
+       //sing up ボタンの有効化
+       dispatch({
+         type: "setIsButtonDisabled",
+         payload: false
+       });
+     }
+   }

  const handleKeyPress = (event: React.KeyboardEvent) => {
    if (event.keyCode === 13 || event.which === 13) {
      state.isButtonDisabled || handleSignup();
    }
  };

  const handleEmailChange: React.ChangeEventHandler<HTMLInputElement> = (
    event
  ) => {
    dispatch({
      type: "setEmail",
      payload: event.target.value
    });
  };

  const handlePasswordChange: React.ChangeEventHandler<HTMLInputElement> = (
    event
  ) => {
    dispatch({
      type: "setPassword",
      payload: event.target.value
    });
  };

  const handlePasswordConfirmChange: React.ChangeEventHandler<HTMLInputElement> = (
    event
  ) => {
    dispatch({
      type: "setPasswordConfirm",
      payload: event.target.value
    });
  };

  return (
    <form className={classes.container} noValidate autoComplete="off">
      <Card className={classes.card}>
        <CardHeader className={classes.header} title="Sign UP " />
        <CardContent>
          <div>
+             {error && <div variant="danger">{error}</div>}
+             {successMessage && <div variant="danger">{successMessage}</div>}
            <TextField
              error={state.isError}
              fullWidth
              id="email"
              type="email"
              label="Email"
              placeholder="Email"
              margin="normal"
              onChange={handleEmailChange}
              onKeyPress={handleKeyPress}
            />
            <TextField
              error={state.isError}
              fullWidth
              id="password"
              type="password"
              label="Password"
              placeholder="Password"
              margin="normal"
              helperText={state.helperText}
              onChange={handlePasswordChange}
              onKeyPress={handleKeyPress}
            />
            <TextField
              error={state.isError}
              fullWidth
              id="password-confirm"
              type="password"
              label="Password-confirm"
              placeholder="Password-confirm"
              margin="normal"
              helperText={state.helperText}
              onChange={handlePasswordConfirmChange}
              onKeyPress={handleKeyPress}
            />
          </div>
          もしアカウントがあるなら Log In
        </CardContent>
        <CardActions>
          <Button
            variant="contained"
            size="large"
            color="secondary"
            className={classes.signupBtn}
            onClick={handleSignup}
            disabled={state.isButtonDisabled}
          >
            Signup
          </Button>
        </CardActions>
      </Card>
    </form>
  );
};

export default Signup;

実際にSignupさせているのがhandleSignupの関数です

  • 通信処理させるのでfunctionにasyncを設定しています。実際に通信させるときはawaitを加えます。
  • 最終的には必要ないのですが、SignUP成功時にメッセージを設定しています
  • tryキャッチでローディング中およびタイムアウトを設定しています。タイムアウトになるとnetwork-request-failedになります

処理が成功するとFirebaseのコンソールにユーザーが追加されます

ログ上にはでないのですが
React Developer ToolsのAuthのプロバイダーの内容を確認すると

currentUserにデータが入っている事が確認できます。

この時点のソース

この画面でもユーザーを作成する事ができます。
React Developer Toolsならユーザーが作成された事が確認できます

この時点のソースです