create-react-app TypeScript Material UIでFirebase authorizationをやる

# はじめに

しばらくさわってなかったら[create-react-appがTypeScriptをサポート](https://create-react-app.dev/docs/adding-typescript/)して開発しやすくなったあげくプロジェクトのソースが関数コンポーネントベースになってた。これは[React Hooks](https://ja.reactjs.org/docs/hooks-intro.html)をちゃんと使わないといけないなと思ったので勉強がてらにタイトルみたいなことをやってみることにしました

できたものは[こちら](https://btf-react-app-auth-demo.web.app/)

# create-react-appをセットアップする

```shell
npm i -g create-react-app
create-react-app firebase-auth-example --typescript
cd firebase-auth-example
yarn start
```
Webブラウザで[localhost:3000](http://localhost:3000/)が表示されたら成功です
![スクリーンショット 2019-11-02 15.51.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/4deaaacb-69de-b366-edd3-ad02adf6ef69.png)


いったん`CTRL-C`でサーバーを停止してFirebaseと[Material UI](https://material-ui.com/)をインストールしておきます

```shell
npm i -S firebase
npm i -S @material-ui/core @material-ui/icons
```

# Firebase Authorization をセットアップする

Firebase Consoleから適当なプロジェクトを作成して`[Autorization] -> ログイン方法 -> ログイン プロバイダ`から`Google`を選択します
![スクリーンショット 2019-11-02 16.15.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/5473c42b-e5e4-4690-ab9a-eb900d46aa99.png)

`有効にする`をONにして`プロジェクトサポートメール`を選択して`保存`をクリックします
![スクリーンショット 2019-11-02 16.17.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/5e69397b-781d-1ef5-3086-af0b72d0b35b.png)

コンソール左上の歯車アイコンをクリックします
![スクリーンショット 2019-11-02 16.22.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/bc3f441b-281e-d3c2-303b-11f35edc160c.png)

マイアプリからWebアプリ`</>`アイコンをクリックします
![スクリーンショット 2019-11-02 16.23.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/23f01da8-c4b7-3067-d4d3-d334b0e8f1ee.png)

アプリのニックネームに適当な名前を入力し`アプリを登録`をクリックします
![スクリーンショット 2019-11-02 16.24.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/251127f4-e31c-b770-7126-1f7bbbd58556.png)

アプリが登録できると以下のようなスクリプトが表示されます

```firebaseConfig.html
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-analytics.js"></script>

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "hogehoge.firebaseapp.com",
    databaseURL: "https://hogehoge.firebaseio.com",
    projectId: "hogehoge",
    storageBucket: "hogehoge.appspot.com",
    messagingSenderId: "999999999999",
    appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    measurementId: "G-XXXXXXXXXXX"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  firebase.analytics();
</script>
```

表示されたスクリプトから`var firebaseConfig = { .... };`の部分をコピーしておきます

# アプリを作成する

シンプルなアプリを作成してみる。Googleアカウントでサインインしてからユーザー名を取得し、ボタンをクリックするとユーザー名を挨拶メッセージに変えるアプリをつくります。せっかくなので挨拶ボタンは別コンポーネントにしてみます

`firebase-auth-example/src/App.tsx`を以下のように変更します

```App.tsx
import React from 'react';
import * as firebase from 'firebase/app';
import "firebase/auth";
import "firebase/analytics";
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import Container from '@material-ui/core/Container';
import Button from '@material-ui/core/Button';
import GreetButton from './GreetButton';

// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "hogehoge.firebaseapp.com",
    databaseURL: "https://hogehoge.firebaseio.com",
    projectId: "hogehoge",
    storageBucket: "hogehoge.appspot.com",
    messagingSenderId: "999999999999",
    appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    measurementId: "G-XXXXXXXXXXX"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.analytics();

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      flexGrow: 1,
    },
    menuButton: {
      marginRight: theme.spacing(2),
    },
    title: {
      flexGrow: 1,
    },
    account: {
      padding: '3vh',
      color: '#555'
    },
  }),
);

const signIn = async () => {
  try {
    await firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL);
    const provider = new firebase.auth.GoogleAuthProvider();
    firebase.auth().signInWithRedirect(provider);
  } catch (e) {
    throw new Error(e);
  }
};

const signOut = async () => {
  try {
    await firebase.auth().signOut();
  } catch (e) {
    throw new Error(e);
  }
};

const App: React.FC = () => {

  const [name, setName] = React.useState<string|null>(null);

  const handleSignIn = async () => {
    await signIn();
  };

  const handleSignOut = async () => {
    await signOut();
    setName(null);
  };
  
  // componentDidMount
  React.useEffect(() => {
    firebase.auth().onAuthStateChanged(async (user) => {
      console.log('User', user);
      if (user && user.displayName) {
        setName(user.displayName);
      }
    });
  },[]);

  const classes = useStyles();
  return (
    <div className="App">
      <AppBar position="static">
        <Toolbar>
          <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" className={classes.title}>
           Auth Example 
          </Typography>
          { name ? (
            <Button color="inherit" onClick={handleSignOut}>Logout</Button>
          ) : (
            <Button color="inherit" onClick={handleSignIn}>Login</Button>
          )}
        </Toolbar>
      </AppBar>
      <React.Fragment>
        <Container maxWidth="sm">
          <Typography component="div" className={classes.account} align="center">
           <h3>{name}</h3> 
          </Typography>
        </Container>
      </React.Fragment>
      <React.Fragment>
        <GreetButton name={name} setName={setName}></GreetButton>
      </React.Fragment>
    </div>
  );
}

export default App;

```

`const firebaseConfig = { .... }`はコピーしておいた`firebaseConfig`に置き換えます

`firebase-auth-example/src/GreetButton.tsx`を新規作成して以下のようにします

```GreetButton.tsx
import React from 'react';

import Typography from '@material-ui/core/Typography';
import Container from '@material-ui/core/Container';
import Button from '@material-ui/core/Button';

type ContentProps = {
  name: string|null;
  setName: React.Dispatch<React.SetStateAction<string|null>>;
}

const GreetButton: React.FC<ContentProps> = (props) => {

  const [disable, setDisable] = React.useState<boolean>(false);

  /* componentDidUpdate */
  React.useEffect(() => {
    if (props.name) {
      setDisable(false);
    } else {
      setDisable(true);
    }
  },[props.name]);

  const handleGreet = async () => { 
    props.setName(`Hello ${props.name}!`);
    setDisable(true);
  };

  return(
    <Container maxWidth="sm">
      <Typography component="div" align="center">
        <Button variant="contained" color="primary" size="large" onClick={handleGreet} disabled={disable}>Greet!</Button>
      </Typography>
    </Container>
  );
};

export default GreetButton;

```

# アプリを実行する

```shell
yarn start
```

ブラウザで以下のようなアプリが表示されたら成功です

![スクリーンショット 2019-11-02 19.09.43.png (14.7 kB)](https://img.esa.io/uploads/production/attachments/3505/2019/11/02/10856/e0f74524-0e11-4ffc-9e31-a2c3590ea8b9.png)

`LOGIN`ボタンをクリックするとGoogleアカウント認証後、アプリにユーザ名が表示されます

![スクリーンショット 2019-11-02 19.12.39.png (17.2 kB)](https://img.esa.io/uploads/production/attachments/3505/2019/11/02/10856/5a4ed9d7-aa8b-41b8-9a81-3095121f48d2.png)

`GREET!`ボタンをクリックすると挨拶メッセージが表示されます

![スクリーンショット 2019-11-02 19.14.13.png (17.7 kB)](https://img.esa.io/uploads/production/attachments/3505/2019/11/02/10856/90dc12d2-7573-4eaf-8cf6-73750e109250.png)

何度もボタンをクリックすると`Hello`が連続してしまうのはこのアプリの仕様ですｗ

# 感想

create-react-app + TypeScript + Material UI が以前より気持ちよく書けるようになった感じがしました。React Hooksのおかげか。Firebase autorizationもasync/awaitで見通しやすく書けるのでいいです。Firebaseを試すためにやってみましたが、いいかんじに実用的なテンプレートができた気がしてます