Chapter 03

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

masalib
masalib
2020.12.21に更新

やりたい事

  • FirebaseAuthを使ってユーザー作成をおこないます

準備作業

Firebaseのコンソールに行ってAuthenticationの「メール/パスワード」を有効化します

https://console.firebase.google.com/u/0/

前回、作成したProjectを選択します。

  1. 左メニューの「Authentication」→「始める」のボタンを押す
  2. 「Sign-in method」を選択して「メール/パスワード」を選択する
  3. 有効化にする(メールリンクは有効化しない)

ローカルにFirebaseのパッケージをインストール

$ cd ProjectFolder
$ npm install Firebase

package.jsonの内容が変更されます

"firebase": "^8.1.1",

設定ファイルを返すプログラム作成

設定ファイルを読み込んでFirebaseとauthのオブジェクトを返すプログラムの作成

/src/firebase.js
import firebase from "firebase/app"
import "firebase/auth"

const firebaseConfig = {
    apiKey: process.env.REACT_APP_APIKEY,
    authDomain: process.env.REACT_APP_AUTHDOMAIN,
    databaseURL: process.env.REACT_APP_DATABASEURL,
    projectId: process.env.REACT_APP_PROJECT_ID,
    storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_APP_ID,
    measurementId: process.env.REACT_APP_MEASUREMENT_ID
};

firebase.initializeApp(firebaseConfig);

var auth_obj = firebase.auth();
export default firebase;
export const auth = auth_obj;

SingUpの画面作成

画面だけ作ります。ロジックはないです

フォルダ構成は以下のとおりです

index.jsの修正

私が参考にしたサイトがコンポーネントをまとめる形になっていたのでそちらにならって
コンポーネントをまとめた形になります

/src/index.js
import React from "react";
import ReactDOM from "react-dom";

+ import App from "./components/App";
- import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);

App.jsの移動と修正

$ cd ProjectFolder
$ cd src
$ mkdir components
$ mv App.js ./components/App.js
/src/components/App.js
import React from "react";
+ import Signup from "./Signup";

export default function App() {
  return (
    <div className="App">
+       <Signup />
-       Hello,World
    </div>
  );
}

Signup.jsの作成

プログラム全文(長文なので注意)
/src/components/Signup.js
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";

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);

  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"
      });
    }
  };

  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>
            <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.jsの補足1

material-uiのスタイルのpropsで指定する方式とHooks式と呼ばれる方式でおこなっています。この方式が正解というわけではないです。

  • ほぼCSSと同じですがjavascriptのため、「-」が使えないため「-」があるプロパティはキャメルケースで記載しています。
      cssの場合:margin-top
      material-uiの場合:marginTop

  • CSSは文字列でも「"」でくくらなくてもいけたのですが、material-uiの場合は必須です。

    cssの場合:display: flex
      material-uiの場合:display: "flex"

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)
    }
  })
);

ちなみにStyled Component式の参考ソース

import React from 'react';
import { styled } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const CustomButton = styled(Button)({
  backgroundColor: 'red'
});

const MaterialUIStyled = () => {
  return (
    <CustomButton>Test</CustomButton>
  );
}
export default MaterialUIStyled;

Signup.jsの補足2

Signupの画面で、State(状態)を設定しています

//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
};

Signup.jsの補足3

状態を管理を変更する関数

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;
  }
};

Signup.jsの補足4

Emailを入力した時に、onChangeイベントが発生してemailのStateを更新してしています。

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

Signup.jsの補足5

state.email, state.password, state.passwordconfirmを監視して
正常の状態になったらボタンを有効化しています

  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]);

Signup.jsの補足6

SingUpのボタンを押した時に起動します。
今は適当な文字列で成功、失敗を判別しています。この部分はあとで直します

 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"
      });
    }
  };

現時点の状態

画面

この時点のソースです

この状態でビューの部分ができたのでロジックを作りたいと思います

参考URL

material-uiの部分

https://qiita.com/h-yoshikawa/items/efa33101b0a02cba7759

https://qiita.com/ss_33_sss/items/d2bc9dc9585da752fc3e