わしのlog

プログラミングとかバイクとか。

React + TypeScript + Firebase でユーザ登録_その1

はじめに

だいきちです。
最近、React + TypeScriptでアプリを作成しています。
その中でユーザ認証周りをFirebaseに任せたいなー
と思いまして、色々調べながら環境とサンプルを作成しました。
情報が古かったり、手順が不十分でわかりにくかったりしたので
GitHubでのサンプル公開と、手順解説をしてみようと思いました。
長いのでパートごとに分けています。

リポジトリ

サンプルのリポジトリはこちら。
github.com

環境

$ npx --version
6.2.0 # npxのバージョン
$ npx create-react-app --version
2.1.3 # create-react-appのバージョン

ここでcreate-react-appがインストールされていない場合は、インストールを行いましょう。

$ npm install -g create-react-app

以後、create-react-appは文中ではCRAとして扱います。
ここまでで準備完了です。
 

CRA + TypeScript

まずはCRA+TypeScriptで環境構築。

$ npx create-react-app [プロジェクト名] --typescript

これでCRA + TypeScriptの環境ができました。
consoleにnpm startと打ちこんで、初期画面が表示されるか確認しましょう。

react-router-domとfirebaseの導入

次に、routingを有効にする為のreact-router-dom
そして、firebaseの導入を行います。
本来はreact-routerは入れなくても良いですが、今後
サンプルを使い回す際に有効かと思い、採用しました。

reac-router-domのインストール

$ npm i -S react-router-dom

※注) @typesが無い場合、TypeScriptではエラーとなる為、下記コマンド実行

$ npm i -S @types/react-router-dom

Firebaseのインストール

$ npm i -S firebase

Firebase側の設定

次に、Firebase側の設定を行います。
まずはFirebaseのコンソールにアクセス。
https://console.firebase.google.com/?hl=ja (Googleへのログインが必要です。)
プロジェクトを追加から画面を開き
プロジェクト名を入力して作成。
f:id:devdaikichi:20190122115024p:plain
今回はチェックは全て無しでOKでしょう。
コンソールに接続したら、左側のサイドバーから
Authenticationを選択します。
ログイン方法を設定を押下し、メール / パスワードを有効化します。
f:id:devdaikichi:20190122115329p:plain  
これで一旦Firebase側の設定は完了です。

認証ページの作成

SignUp用のページを作成します。
今回は/src配下にSignUp.tsxというファイルを作成します。
cssは楽したいのでMaterial-UIを使用します。

$ npm i @material-ui/core @material-ui/icons

SignUp.tsxに下記コードを書きます。

import React from "react";
import Paper from '@material-ui/core/Paper';
import Input from '@material-ui/core/Input';
import FormControl from '@material-ui/core/FormControl';
import InputLabel from '@material-ui/core/InputLabel';
import Button from '@material-ui/core/Button';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import withStyles, {StyleRules, WithStyles} from '@material-ui/core/styles/withStyles';
import {Theme} from '@material-ui/core/styles/createMuiTheme';
import Checkbox from "@material-ui/core/Checkbox/Checkbox";
import Typography from "@material-ui/core/Typography/Typography";
import Avatar from "@material-ui/core/Avatar/Avatar";
import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
import CssBaseline from "@material-ui/core/CssBaseline/CssBaseline";

const styles = (theme: Theme): StyleRules => ({
  main: {
    width: 'auto',
    display: 'block', // Fix IE 11 issue.
    marginLeft: theme.spacing.unit * 3,
    marginRight: theme.spacing.unit * 3,
    [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
      width: 400,
      marginLeft: 'auto',
      marginRight: 'auto',
    },
  },
  paper: {
    marginTop: theme.spacing.unit * 8,
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`,
  },
  avatar: {
    margin: theme.spacing.unit,
    backgroundColor: theme.palette.secondary.main,
  },
  form: {
    width: '100%', // Fix IE 11 issue.
    marginTop: theme.spacing.unit,
  },
  submit: {
    marginTop: theme.spacing.unit * 3,
  },
});

interface Props extends WithStyles<typeof styles> {
}

// SignUp用のinterface
interface SignUpObject {
  username: string,
  email: string,
  password: string,
  error: any | null,
}

class SignUp extends React.Component<Props, SignUpObject> {
  constructor(props: any) {
    super(props);
    this.state = {
      username: "",
      email: "",
      password: "",
      error: null
    };
  }

  // input要素が変更された時のハンドリング
  private handleChange = (event: any) => {
  };
  // submitされた時の挙動
  private handleSubmit = async (event: any) => {
  };
  // エラーハンドリング
  private handleError = () => {
  };

  render() {
    const state = this.state;
    const {classes} = this.props;
    return (
      <main className={classes.main}>
        <CssBaseline />
        <Paper className={classes.paper}>
          <Avatar className={classes.avatar}>
            <LockOutlinedIcon/>
          </Avatar>
          <Typography component="h1" variant="h5">
            Sign in
          </Typography>
          <form onSubmit={this.handleSubmit} className={classes.form}>
            {state.error !== null && this.handleError()}
            <FormControl margin="normal" required fullWidth>
              <InputLabel htmlFor="username">UserName</InputLabel>
              <Input name={"username"} id="username" value={state.username} onChange={this.handleChange}/>
            </FormControl>
            <FormControl margin="normal" required fullWidth>
              <InputLabel htmlFor="email">Email Address</InputLabel>
              <Input name={"email"} id="email" value={state.email} onChange={this.handleChange}/>
            </FormControl >
            <FormControl margin="normal" required fullWidth>
              <InputLabel htmlFor="password">Password</InputLabel>
              <Input name={"password"} id="password" type="password" value={state.password}
                     onChange={this.handleChange}/>
            </FormControl>
            <FormControlLabel
              control={<Checkbox value="remember" color="primary"/>}
              label="Remember me"
            />
            <Button
              type="submit"
              fullWidth
              variant="contained"
              color="primary"
              className={classes.submit}
            >
              Sign in
            </Button>
          </form>
        </Paper>
      </main>
    );
  }
}

export default withStyles(styles)(SignUp);

これで認証ページができました。
style周りはMaterial-UIのPage Layout Examplesから拝借してきたので
少しごちゃごちゃしていますが、ご勘弁を。
ハンドリング用の関数については次の記事で書きます。

routingの実装

次に、react-router-domを使用してroutingを完成させます。
App.tsxをこんな感じに編集。

+ import React from 'react';
+ import {BrowserRouter, Link, Route} from "react-router-dom";
import logo from './logo.svg';
import './App.css';
+ import SignUp from "./SignUp";
+ import AppBar from "@material-ui/core/AppBar/AppBar";
+ import Toolbar from "@material-ui/core/Toolbar/Toolbar";
+ import Button from "@material-ui/core/Button/Button";

+ const App = () => (
+   <BrowserRouter>
+     <div>
+       <AppBar position="static" color="default">
+         <Toolbar>
+           <Button variant="contained">
+             <Link to={'/'}>home</Link>
+           </Button>
+           <Button variant="contained">
+             <Link to={'/signup'}>signup</Link>
+           </Button>
+         </Toolbar>
+       </AppBar>
+       <Route exact path='/' component={DefaultApp}/>
+       <Route path='/signup' component={SignUp}/>
+     </div>
+   </BrowserRouter>
+ );

+ class DefaultApp extends React.Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
...

classであるDefaultApp内は、元のApp.tsxでrenderしていた内容と同一です。
Appとしてexportしている箇所にroutingを実装しています。
詳しくは解説しませんが、知りたい方は下記記事参照。
react-router@v4を使ってみよう:シンプルなtutorial - Qiita
これでメイン画面にボタンが2つ表示され、signupをクリックすると
認証ページに飛ぶことが確認できるはずです。

とりあえず今回はここまで。
続きの記事も書き溜めているので、随時公開していきます。


現場からは以上です。