React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発③

前回ではReduxで状態管理を設定し，Firebaseと接続，firestoreデータベースとの同期まで漕ぎ着けた．

今回は`Firebase Authentication`を使ってログイン機能を実装していきたい．

[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発①](https://qiita.com/momosuke/items/245eabb8dbdc3493aac0)　
[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発②](https://qiita.com/momosuke/items/57295ca99387ab243ce8)　
[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発③]()　←今ここ！！！
[React + Redux + Firebase を使ってログイン機能あり掲示板アプリ開発④](https://qiita.com/momosuke/items/0fa933f8b6e470e5796d)

#Firebase Authenticationイントロ
`Firebase Authentication`は認証機能を司る機能で`Firestore`などと連動することが可能．

また一種のデータベースのような振る舞いもする．ユーザーが登録した時にユーザー情報を保管する．firestoreデータベースにではなく．

例えばメールアドレスやユーザーID（Authenticationによる自動生成）などが保管できる．しかし自由度は低く，名前や，パスワードなどのカスタムプロパティは保存できない．

なのでそれらカスタムプロパティは`firestore`に`Users`コレクションを作成し，そこに保存することにする．
そこにAuthenticationで作成したIDでもってデータを保管すればAuthentication内のデータとの結びつけもできることになる．
<img width="1361" alt="スクリーンショット 2019-10-26 14.08.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/c9406b3f-9d44-77b0-bb9e-3313624eec4c.png">

まずはFirebase Authenticationを利用可能にしてみよう．
firebaseのダッシュボードから`Authentication`ページに行って，`ログイン方法を設定`をクリック，

FacebookやTwitterなどたくさんのログインメソッドが出てくる．今回は単純に`メール/パスワード`を有効にする．（メールリンクは無効のままで）

ユーザータブにとんで，テストユーザーを作ってみよう．
<img width="1680" alt="スクリーンショット 2019-10-26 14.17.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/6263c728-708f-5c18-5ef3-0900b9cac2c6.png">

こいつはまだ追加の情報を持ってないし，firestoreで連携していない．
最終的には`Users`コレクションを作って，ユーザと連携するようにしたい．

#ReduxとFirebase Authの接続
Firebase Authの設定が完了したので次はアプリ内で使えるようにしたい．具体的にはユーザーのAuthenticationステータスを常に追っている状態にしたい．ログインしたのか，ログアウトしたのか．というようなステータスを．

そのためには`firebase`から`Firebase Authentication`のステータスを`redux`のstoreに同期して`state`からステータスを確認できるようにする必要がある．

前回，`firestoreReducer`を使って，`firestore`と`state`を同期した．今回も似たようなことをすればいい．

ということで`rootReducer`に`firebaseReducer`を追加する．

```reducers/rootReducer.js
import authReducer from './authReducer'
import projectReducer from './projectReducer'
import { combineReducers } from 'redux'
import { firestoreReducer } from 'redux-firestore'
import { firebaseReducer } from 'react-redux-firebase'

const rootReducer = combineReducers({
    auth: authReducer,
    project: projectReducer,
    firestore: firestoreReducer,
    firebase: firebaseReducer
})

export default rootReducer
```
`firebaseReducer`によってfirebaseの全てのステータスが`redux`ひいては`state`に同期される．stateの`firebase`オブジェクトにね．

これでセットアップは完了した．では実際にコンポネントからAuthenticationステータスにアクセスしてみよう．

今回は`Navbar`コンポネントでアクセスしていく．ここでは`SignedInLinks`と`SignedOutLinks`コンポネントが呼び出されているが，①で書いたように本来はログイン状況に応じて片方のみを表示したいのだ．

まずはコンポネントを`connect`を使って`Redux`を接続している．ここでは`firestore`のデータはいらない．つまりHOCは1つなので`compose`はいらない．

とりあえずどんな形式で`state`に保存されているのか知るために`console.log(state)`だけしてみる．

```layout/Navbar.js
import React from 'react'
import { Link } from 'react-router-dom'
import SignedInLinks from './SignedInLinks'
import SignedOutLinks from './SignedOutLinks'
import { connect } from 'react-redux'

const Navbar = () => {
    return (
        <nav className="nav-wrapper grey darken-3">
            <div className="container">
                <Link to='/' className="brand-logo">MarioPlan</Link>
                <SignedInLinks />
                <SignedOutLinks />
            </div>
        </nav>
    )
}

const mapStateToProps = (state) => {
    console.log(state);
    return {

    }
}

export default connect(mapStateToProps)(Navbar);
```
実行してコンソールから確認してみると，
<img width="1680" alt="スクリーンショット 2019-10-26 14.54.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/c8cea7b0-151e-f5ce-28c5-bb08c168c5c3.png">
`firebase`の`auth`プロパティをみると`isEmpty`が`true`になっている．これはプロフィールが存在しないことを表している．つまりログインしていないことを表す．

次はログイン機能を実装していく．

#ログイン機能
メールアドレスとパスワードを使ってログインするにはfirebaseプロジェクトとの非同期処理を行う必要がある．

それはどこでやればいいか．もうわかるよね．`action creator`だ．
ということでサインインに関するaction creatorを作成していく．

おさらいだけど`thunk`のおかげで`dispatch`を一旦止めて代わりに関数を返すことができたよね．そこで`dispatch`やfirebaseに関するインスタンスを作る`getFirebase`を受け取って非同期処理が行えた．

今回は`auth`関数，そして`signInWithEmailAndPassword`メソッドを使って引数に必要な情報を渡すことでログインできる．

そして`projectActions.js`同様，非同期処理には幾分か時間がかかることを考慮して`then`メソッドを使ってコールバック関数を呼び出し，その中で`action`を`dispatch`する．

```actions/authActions.js
export const signIn = (credentials) => {
    return (dispatch, getState, {getFirebase}) => {
        const firebase = getFirebase();

        firebase.auth().signInWithEmailAndPassword(
            credentials.email,
            credentials.password
        ).then(() => {
            dispatch({ type: 'LOGIN_SUCCESS' });
        }).catch((err) => {
            dispatch({ type: 'LOGIN_ERROR' });
        });
    }
}
```
`credentials`にはメールアドレスやパスワードが含まれる．

次にこれらの`action`を扱う`reducer`を`authReducer.js`で実装する．

ログインが成功したかどうかという指標として`authError`というプロパティを与えた．

```reducers/authReducer.js
const initState = {
    authError: null
}
const authReducer = (state = initState, action) => {
    switch(action.type) {
        case 'LOGIN_ERROR':
            console.log('login error');
            return {
                ...state,
                authError: 'Login failed'
            }
        case 'LOGIN_SUCCESS':
            console.log('login success');
            return {
                ...state,
                authError: null
            }
        default:
            return state;
    }
}

export default authReducer
```

注意として`rootReducer`を見ればわかるがここでの`state`はreduxストアの`state.auth`に対応する．`state.firebase`ではなくて．

あとは`SignIn`コンポネントから`authActions`アクションクリエータを`dispatch`すればいい．
そのためには`authActions`を孕んだ`disaptch`を`props`としてコンポネントに渡す．方法はもちろん`connnect`．

```auth/SignIn.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { signIn } from '../../store/actions/authActions'

class SignIn extends Component {
    state = {
        email: '',
        password: ''
    }
    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        this.props.signIn(this.state)
    }
    render() {
        const { authError } = this.props;
        return (
            <div className="container">
                <form onSubmit={this.handleSubmit} className="white">
                    <h5 className="grey-text text-darken-3">Sign In</h5>
                    <div className="input-field">
                        <label htmlFor="email">Email</label>
                        <input type="email" id="email" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="password">Password</label>
                        <input type="password" id="password" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <button className="btn pink lighten-1 z-depth-0">Login</button>
                        <div className="red-text center">
                            { authError ? <p>{authError}</p> : null }
                        </div>
                    </div>
                </form>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        authError: state.auth.authError
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        signIn: (creds) => dispatch(signIn(creds))
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SignIn)
```
エラーがあったとき用に`mapStateToProps`で`authError`を渡してフォーム下部でログイン失敗すると`authError`が表示されるようにした．
<img width="1680" alt="スクリーンショット 2019-10-26 15.58.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/7a0fc830-0753-8730-19ce-221812900a3c.png">
わざとログイン失敗して`authError`が表示されるのも確認してみて欲しい．

次はログアウトを実装したいと思う．

#ログアウト機能
ログアウト用のコンポネントは作ってなかったよね．
`SignedInkLinks`の`Log Out`をクリックすると即座にログアウトする．そんな設計だった．今はクリックしてもダッシュボードに飛ぶだけ．

ログアウトもログイン同様，非同期処理でもって遂行する．ということは実装するのはもちろん`action creator`上だ．

ということで`authActions`をいじってログアウト用の`action creator`を書いていく．

```actions/authActions.js
export const signIn = (credentials) => {
    return (dispatch, getState, {getFirebase}) => {
        const firebase = getFirebase();

        firebase.auth().signInWithEmailAndPassword(
            credentials.email,
            credentials.password
        ).then(() => {
            dispatch({ type: 'LOGIN_SUCCESS' });
        }).catch((err) => {
            dispatch({ type: 'LOGIN_ERROR' });
        });
    }
}

export const signOut = () => {
    return (dispatch, getState, {getFirebase}) => {
        const firebase = getFirebase();

        firebase.auth().signOut().then(() => {
            dispatch({ type: 'SIGNOUT_SUCCESS' })
        });
    }
}
```
ログイン用のと同様に`firebase`インスタンスを作ってログアウト用のメソッドを実行している．
そして非同期処理が完了次第，`action`を`dispatch`している．

次に，この`action`を扱う`reducer`を定義する．ここもログイン用と同じだな．

```reducers/authReducer.js
const initState = {
    authError: null
}
const authReducer = (state = initState, action) => {
    switch(action.type) {
        case 'LOGIN_ERROR':
            console.log('login error');
            return {
                ...state,
                authError: 'Login failed'
            }
        case 'LOGIN_SUCCESS':
            console.log('login success');
            return {
                ...state,
                authError: null
            }
        case 'SIGNOUT_SUCCESS':
            console.log('signout success');
            return state;
        default:
            return state;
    }
}

export default authReducer
```

あとは`Log Out`をクリックしたら`signOut`アクションクリエータをfire！してくれるようにします．

まずはアクションクリエータを孕んだ`dispatch`を`props`としてコンポネントに渡すために`mapDispatchToProps`を`connect`していく．

そして`Log Out`のところを`a`タグに変更する．

```layout/SignedInLinks.js
import React from 'react'
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { signOut } from '../../store/actions/authActions'

const SignedInLinks = (props) => {
    return (
            <ul className="right">
            <li><NavLink to='/create'>New Project</NavLink></li>
            <li><a onClick={props.signOut}>Log Out</a></li>
            <li><NavLink to='/' className="btn btn-floating pink lighten-1">NN</NavLink></li>
        </ul>
    )
}

const mapDispatchToProps = (dispatch) => {
    return {
        signOut: () => dispatch(signOut())
    }
}

export default connect(null, mapDispatchToProps)(SignedInLinks);
```

ログインしてからログアウトして，ブラウザで確認してみよう．
<img width="1680" alt="スクリーンショット 2019-10-26 16.43.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/9d8964f9-ea97-9f7b-4d59-46c5de78b96b.png">

次はログイン状況に応じて`Navbar`上の`SignedInLinks`と`SignedOutLinks`の表示切り替えを実装したい．

#Authステータスのトラッキング

二つのコンポネントをラップしてる`Navbar`コンポネントをいじるしかないだろう．
ログインしているとき，`state.firebase.auth`にはユーザーID示す`uid`が存在する．ログインしていないと存在しない．
これを利用して片方のコンポネントのみ表示させる．

```layout/Navbar.js
import React from 'react'
import { Link } from 'react-router-dom'
import SignedInLinks from './SignedInLinks'
import SignedOutLinks from './SignedOutLinks'
import { connect } from 'react-redux'

const Navbar = (props) => {
    const { auth } = props;
    const links = auth.uid ? <SignedInLinks /> : <SignedOutLinks />
    return (
        <nav className="nav-wrapper grey darken-3">
            <div className="container">
                <Link to='/' className="brand-logo">MarioPlan</Link>
                { links }
            </div>
        </nav>
    )
}

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth
    }
}

export default connect(mapStateToProps)(Navbar);
```
ログインしたりログアウトしたりしてブラウザで確認しよう．
<img width="1680" alt="スクリーンショット 2019-10-26 17.00.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/d90cf6ef-962f-5e80-a467-791c4cb62650.png">
<img width="1680" alt="スクリーンショット 2019-10-26 17.00.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/32126fc2-22a2-263e-c88c-30d8e4a4fadb.png">

次は今のと似たような条件分岐を使って軽微な調整をする．

#Authの準備をまってやれ
`Firebase Authentication`を使ったシステムはほぼ完成した．
しかし少し問題が生じている．

ブラウザでログインした状態でリロードすると，一瞬ではあるが，`Navbar`に`SignedOutLinks`が表示されてしまっている．
これはよくないUXの例だね．

`Firebase Authentication`が初期化される前にコンポネントひいてはアプリ全体がレンダリングされてしまうと，ログインできていないステータスで表示される．さっきで言うところの，`uid`が存在していないからだ．

なので初期化が済んでログイン状況がわかるまでは，アプリがDOMにレンダリングされるのを阻止したい．

解決はとても簡単なのでサクッとやってしまおう．

`src/index.js`をいじる．ここがアプリをレンダリングしている場所だ．

`react-redux-firebase`ストアエンハンサーの引数で`attachAuthIsReady`を`true`にする．
これによって`store`のプロパティで`firebaseAuthIsReady`というメソッドが呼び出せるようになる．

これを呼び出すとfirebaseの初期化をまってくれて，そこに`then`メソッドを使ってコールバック関数を呼び出し，その中でアプリをDOMにレンダリングする．

こうすれば，`firebase初期化`→`アプリをDOMにレンダリング`という順番が確定する．

```src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from './store/reducers/rootReducer';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { reduxFirestore, getFirestore } from 'redux-firestore';
import { reactReduxFirebase, getFirebase } from 'react-redux-firebase';
import fbConfig from './config/fbConfig';

const store = createStore(rootReducer,
    compose(
        applyMiddleware(thunk.withExtraArgument({getFirebase, getFirestore})),
        reduxFirestore(fbConfig),
        reactReduxFirebase(fbConfig, {attachAuthIsReady: true}) 
    )
);

store.firebaseAuthIsReady.then(() => {
    ReactDOM.render(
        <Provider store={store}><App /></Provider>, document.getElementById('root'));
    serviceWorker.unregister();
})
```
ブラウザでリロードして修正できたのを確認してみよう．

#ルートガード
今回のアプリでは，ユーザーがサイトを訪れた時（ログインしてない時）にはプロジェクト一覧とディティールを見せたくないし，新しいプロジェクトも作らせたくない．
なのでログインしていないユーザーが`/`や`/create`，`/project/:id`にアクセスしてもログインページ`/signin`にリダイレクトして欲しい．
それにはルートガードという手法をとる．

早速`Dashboard`からいじって行こう．

`state.firebase.auth`でログイン状況が分かるんだったよね．こいつをコンポネントで使うために`mapStateToProps`にて`props`に渡す．

ログインしていないと`!auth.uid`が`true`となり，`react-router-dom`から提供されている`Redirect`で`/signin`へリダイレクトする．

```dashboard/Dashboard.js
import React, { Component } from 'react'
import Notification from './Notification'
import ProjectList from '../projects/ProjectList'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'

class Dashboard extends Component {
    render() {
        const { projects, auth } = this.props;
        if (!auth.uid) return <Redirect to='/signin' />

        return (
            <div className="dashboard container">
                <div className="row">
                    <div className="col s12 m6">
                        <ProjectList projects={projects} />
                    </div>
                    <div className="col s12 m5 offset-m1">
                        <Notification />
                    </div>
                </div>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    console.log(state);
    return {
        projects: state.firestore.ordered.projects,
        auth: state.firebase.auth
    }
}

export default compose(
    connect(mapStateToProps),
    firestoreConnect([
        { collection: 'projects' }
    ])
)(Dashboard);
```
これで`/`用のルートガードができた．次は`/create`用のルートガードだ．ログインしてないから`New Project`ボタンは押せないが`/create`と直接url入力すれば訪問できてしまっている．

```projects/CreateProject.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createProject } from '../../store/actions/projectActions'
import { Redirect } from 'react-router-dom'

class CreateProject extends Component {
    state = {
        title: '',
        content: ''
    }
    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        this.props.createProject(this.state)
    }
    render() {
        const { auth } = this.props;
        if (!auth.uid) return <Redirect to='/signin' />

        return (
            <div className="container">
                <form onSubmit={this.handleSubmit} className="white">
                    <h5 className="grey-text text-darken-3">Create new project</h5>
                    <div className="input-field">
                        <label htmlFor="title">Title</label>
                        <input type="text" id="title" onChange={this.handleChange} />
                    </div>
                    <div className="input-field">
                        <label htmlFor="content">Project Content</label>
                        <textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
                    </div>
                    <div className="input-field">
                        <button className="btn pink lighten-1 z-depth-0">Create</button>
                    </div>
                </form>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        createProject: (project) => dispatch(createProject(project))
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(CreateProject)
```
同様のことを`ProjectDetails`でも実装する．

```projects/ProjectDetails.js
import React from 'react'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'

const ProjectDetails = (props) => {
    const { project, auth } = props;
    if (!auth.uid) return <Redirect to='/signin' />
    if (project) {
        return (
            <div className="container section project-details">
                <div className="card z-depth-0">
                    <div className="card-content">
                        <span className="card-title">{ project.title }</span>
                        <p>{ project.content }</p>
                    </div>
                    <div className="card-action gret lighten-4 grey-text">
                        <div>Posted by {project.authorFirstName} {project.authorLastName}</div>
                        <div>2nd, September, 2am</div>
                    </div>
                </div>
            </div>
        )
    } else {
        return (
            <div className="container center">
                <p>Loaging project...</p>
            </div>
        )
    }
    
}

const mapStateToProps = (state, ownProps) => {
    const id = ownProps.match.params.id;
    const projects = state.firestore.data.projects;
    const project = projects ? projects[id] : null
    return {
        project: project,
        auth: state.firebase.auth
    }
}

export default compose(
    connect(mapStateToProps),
    firestoreConnect([
        { collection: 'projects' }
    ])
)(ProjectDetails);
```
各自ブラウザでルートガードができているか確認して欲しい．

あとは逆にログインしているのに`/signin`や`/signup`にはアクセスして欲しくないのでそこもルートガードを実装する必要がある．

まずは`SignIn`コンポネントからいじろうか．

```auth/SignIn.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { signIn } from '../../store/actions/authActions'
import { Redirect } from 'react-router-dom'

class SignIn extends Component {
    state = {
        email: '',
        password: ''
    }
    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        this.props.signIn(this.state)
    }
    render() {
        const { authError, auth } = this.props;
        if (auth.uid) return <Redirect to='/' />

        return (
            <div className="container">
                <form onSubmit={this.handleSubmit} className="white">
                    <h5 className="grey-text text-darken-3">Sign In</h5>
                    <div className="input-field">
                        <label htmlFor="email">Email</label>
                        <input type="email" id="email" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="password">Password</label>
                        <input type="password" id="password" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <button className="btn pink lighten-1 z-depth-0">Login</button>
                        <div className="red-text center">
                            { authError ? <p>{authError}</p> : null }
                        </div>
                    </div>
                </form>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        authError: state.auth.authError,
        auth: state.firebase.auth
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        signIn: (creds) => dispatch(signIn(creds))
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SignIn)
```
次は`SignUp`だ．

```auth/SignUp.js
import React, { Component } from 'react'
import { Redirect } from 'react-router-dom'
import { connect } from 'react-redux'

class SignUp extends Component {
    state = {
        email: '',
        password: '',
        firstName: '',
        lastName: ''
    }
    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        console.log(this.state)
    }
    render() {
        const { auth } = this.props;
        if (auth.uid) return <Redirect to='/' />

        return (
            <div className="container">
                <form onSubmit={this.handleSubmit} className="white">
                    <h5 className="grey-text text-darken-3">Sign Up</h5>
                    <div className="input-field">
                        <label htmlFor="email">Email</label>
                        <input type="email" id="email" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="password">Password</label>
                        <input type="password" id="password" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="firstName">First Name</label>
                        <input type="text" id="firstName" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="lastName">Last Name</label>
                        <input type="text" id="lastName" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <button className="btn pink lighten-1 z-depth-0">Sign up</button>
                    </div>
                </form>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth
    }
}

export default connect(mapStateToProps)(SignUp);
```

これで一通りのルートガードの実装が完了した．やったことを振り返ると`Authentication`のステータスを利用して条件に応じて`Redirect`させることでコンテンツを保護した．

#サインアップ機能
今はまだ`SIGN UP`を押しても内部`state`に保管された入力内容を`console.log(state)`するだけに留まっている．
本当はFirebase Authを使って新しいユーザーを作成したい．

どこに実装すればいい？非同期処理タスクを扱うんだからもちろん`action creator`だよね．

なので`authActions.js`内にサインアップ用のアクションクリエータを作って行こう．

```actions/authActions.js
export const signIn = (credentials) => {
    return (dispatch, getState, {getFirebase}) => {
        const firebase = getFirebase();

        firebase.auth().signInWithEmailAndPassword(
            credentials.email,
            credentials.password
        ).then(() => {
            dispatch({ type: 'LOGIN_SUCCESS' });
        }).catch((err) => {
            dispatch({ type: 'LOGIN_ERROR' });
        });
    }
}

export const signOut = () => {
    return (dispatch, getState, {getFirebase}) => {
        const firebase = getFirebase();

        firebase.auth().signOut().then(() => {
            dispatch({ type: 'SIGNOUT_SUCCESS' })
        });
    }
}

export const signUp = (newUser) => {
    return (dispatch, getState, {getFirebase, getFirestore}) => {
        const firebase = getFirebase();
        const firestore = getFirestore();

        firebase.auth().createUserWithEmailAndPassword(
            newUser.email,
            newUser.password
        ).then((resp) => {
            return firestore.collection('users').doc(resp.user.uid).set({
                firstName: newUser.firstName,
                lastName: newUser.lastName,
                initials: newUser.firstName[0] + newUser.lastName[0]
            })
        }).then(() => {
            dispatch({ type: 'SIGNUP_SUCCESS' })
        }).catch(err => {
            dispatch({ type: 'SIGNUP_ERROR', err })
        })
    }
}
```

他の`signIn`と`signOut`と違い，`getFirestore`も使うのは理由がある．
`Authentication`ではメールと（自動生成の）IDだけしか情報を持たない．しかし名前などの追加情報も保管したいので`firestore`で`Users`コレクションを作成し，そこに個人データを保管する．
この時`Authentication`で自動生成された`ID`が`Users`コレクションに保管されるデータのユニークIDとを一致させることで連携させるためだ．
<img width="1361" alt="スクリーンショット 2019-10-26 14.08.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/c9406b3f-9d44-77b0-bb9e-3313624eec4c.png">

`newUser`にはフォームに入力された情報が，`resp`には`createUserWithEmailAndPassword`の返り値が入っている．
`then`メソッドのコールバック関数内で`users`コレクションにデータを追加している．`users`コレクションは作成していないが，存在しないコレクション名を指定すると自動で作ってくれるから問題ない．

注意として`add`だと自動でデータに対してユニークなIDがつくが今回はそれを望まない．なぜなら`Authentication`で自動生成されたIDと一致させたいからだ．

今回は`doc`を使用することで特定のIDを指定できるようにした．そこに`set`メソッドで追加するデータを指定する．
あとはいつも通り成功時とエラー時の`action`を`dispatch`している．

次にこれらの`action`を扱う`reducer`を設定していく．

```reducers/authReducer.js
const initState = {
    authError: null
}
const authReducer = (state = initState, action) => {
    switch(action.type) {
        case 'LOGIN_ERROR':
            console.log('login error');
            return {
                ...state,
                authError: 'Login failed'
            }
        case 'LOGIN_SUCCESS':
            console.log('login success');
            return {
                ...state,
                authError: null
            }
        case 'SIGNOUT_SUCCESS':
            console.log('signout success');
            return state;
        case 'SIGNUP_SUCCESS':
            console.log('signup success');
            return {
                ...state,
                authError: null
            }
        case 'SIGNUP_ERROR':
            console.log('signup failed');
            return {
                ...state,
                authError: action.err.message
            }
        default:
            return state;
    }
}

export default authReducer
```
`action.type.message`には`無効なメールアドレスだ`とか`パスワードが短すぎる`とかのエラーメッセージが含まれる．

これで呼び出される側の実装は終わった．あとは呼び出す側`SignUp`コンポネントでの実装のみだ．やっていこう．

```auth/SignUp.js
import React, { Component } from 'react'
import { Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
import { signUp } from '../../store/actions/authActions'

class SignUp extends Component {
    state = {
        email: '',
        password: '',
        firstName: '',
        lastName: ''
    }
    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault();
        this.props.signUp(this.state);
    }
    render() {
        const { auth, authError } = this.props;
        if (auth.uid) return <Redirect to='/' />

        return (
            <div className="container">
                <form onSubmit={this.handleSubmit} className="white">
                    <h5 className="grey-text text-darken-3">Sign Up</h5>
                    <div className="input-field">
                        <label htmlFor="email">Email</label>
                        <input type="email" id="email" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="password">Password</label>
                        <input type="password" id="password" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="firstName">First Name</label>
                        <input type="text" id="firstName" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <label htmlFor="lastName">Last Name</label>
                        <input type="text" id="lastName" onChange={this.handleChange}/>
                    </div>
                    <div className="input-field">
                        <button className="btn pink lighten-1 z-depth-0">Sign up</button>
                        <div className="red-text center">
                            { authError ? <p>{authError}</p> : null }
                        </div>
                    </div>
                </form>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth,
        authError: state.auth.authError
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        signUp: (newUser) => dispatch(signUp(newUser))
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SignUp);
```

それではブラウザでサインアップしてみよう．
password: 'test1234'
<img width="1680" alt="スクリーンショット 2019-10-26 22.29.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/9ecc84ad-b850-b19f-082e-c49428db914a.png">

サインアップが無事完了してルートガードによってダッシュボード画面にきたらOK．

あとはfirebaseプロジェクトにユーザーが追加されているかも確認しよう．
<img width="1680" alt="スクリーンショット 2019-10-26 22.33.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/5e510fc2-a8e7-c14a-1267-5f0bacebd9b2.png">
<img width="1680" alt="スクリーンショット 2019-10-26 22.33.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/09e8bcb1-fa15-0947-3caf-eccb39fe210f.png">
`Authentication`の`ユーザーUID`と`Firestore`のドキュメントの`id`が一致しているのが分かるだろう．

あと`SignIn`と同様にエラーの時に`authError`を使ってフォーム下部に赤字でエラーメッセージ表示するようにもしたので，わざと短いパスワードにしたりして動作確認してね．

#ユーザープロフィールデータ
次は`Navbar`にログイン中に出てくるイニシャルが描かれたピンクの円をログインユーザーに合わせて設定したい．

今のところ`Navbar`も`SignedInLinks`もユーザーのメールアドレスやIDの情報は持っているが，名前の情報は持っていない．

↓`Navbar`の`mapStateToProps`内に`console.log(state)`を設置してみた画像
<img width="1680" alt="スクリーンショット 2019-10-26 23.09.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/e947662c-4244-f7b1-3433-56a97ffd6781.png">
じゃぁ名前の情報はどこにあるかと言ったら`Firestore`の`users`コレクションだった．
`firebase.profile`というプロパティがある．画像での状態はプロフィール情報を持っていないことを示す．ここに名前情報などを持ったオブジェクトをぶち込みたい．

実は方法はすごい簡単で，`src/index.js`の`react-redux-firebase`に追加のプロパティを設定してあげればいい．

```src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from './store/reducers/rootReducer';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { reduxFirestore, getFirestore } from 'redux-firestore';
import { reactReduxFirebase, getFirebase } from 'react-redux-firebase';
import fbConfig from './config/fbConfig';

const store = createStore(rootReducer,
    compose(
        applyMiddleware(thunk.withExtraArgument({getFirebase, getFirestore})),
        reduxFirestore(fbConfig),
        reactReduxFirebase(fbConfig, {useFirestoreForProfile: true, userProfile: 'users', attachAuthIsReady: true}) 
    )
);

store.firebaseAuthIsReady.then(() => {
    ReactDOM.render(
        <Provider store={store}><App /></Provider>, document.getElementById('root'));
    serviceWorker.unregister();
})
```
こいつらは`react-redux-firebase`と`redux-firestore`経由でユーザー情報を取得するための設定だ．

`useFirestoreForProfile`は`firebaseReducer`に`firestore`データベースと`profile`オブジェクトを同期を可能にさせる設定だ．ただこのままでは`firebaseReducer`はどのコレクションと同期すればいいのか知らない．
なので，`userProfile`で`users`とコレクション名を指定してあげた．

この状態でブラウザコンソールを確認してみよう．（他コンポネントのconsole.logとか残ってたら適宜削除してね）
<img width="1680" alt="スクリーンショット 2019-10-26 23.34.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/8f8129b0-9fe8-e144-e80b-c53d0813c1ed.png">
見事`firebase.profile`にログインしているユーザーの情報がぶち込まれた．
これをもとに`Navbar`のピンク円を編集していく．

いつも通り`mapStateToProps`で`profile`を`props`に渡し，さらにコンポネント内で`SignedInLinks`に`props`として渡している．

```layout/Navbar.js
import React from 'react'
import { Link } from 'react-router-dom'
import SignedInLinks from './SignedInLinks'
import SignedOutLinks from './SignedOutLinks'
import { connect } from 'react-redux'

const Navbar = (props) => {
    const { auth, profile } = props;
    const links = auth.uid ? <SignedInLinks profile={profile} /> : <SignedOutLinks />
    return (
        <nav className="nav-wrapper grey darken-3">
            <div className="container">
                <Link to='/' className="brand-logo">MarioPlan</Link>
                { links }
            </div>
        </nav>
    )
}

const mapStateToProps = (state) => {
    console.log(state);
    return {
        auth: state.firebase.auth,
        profile: state.firebase.profile
    }
}

export default connect(mapStateToProps)(Navbar);
```

最後に`SignedInLinks`をいじって終わりだ．


```layout/SignedInLinks.js
import React from 'react'
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { signOut } from '../../store/actions/authActions'

const SignedInLinks = (props) => {
    return (
            <ul className="right">
            <li><NavLink to='/create'>New Project</NavLink></li>
            <li><a onClick={props.signOut}>Log Out</a></li>
            <li><NavLink to='/' className="btn btn-floating pink lighten-1">
                {props.profile.initials}
            </NavLink></li>
        </ul>
    )
}

const mapDispatchToProps = (dispatch) => {
    return {
        signOut: () => dispatch(signOut())
    }
}

export default connect(null, mapDispatchToProps)(SignedInLinks);
```

#プロジェクト作成の細部
`CreateProject`でプロジェクトを作成すると`createproject`アクションクリエータが実行される．その内部ではfirestoreの`projects`コレクションにプロジェクトを追加している．

しかし，`author~~`の3つのプロパティは前にハードコーディングしたものをまだ使っている．これをログインしているユーザーの情報に対応させたいので`projectActions.js`を修正していく．

`getState`を使って`state`のデータにアクセスする．`getState()`はstate全体を返す．

```actions/projectActions.js
export const createProject = (project) => {
    return (dispatch, getState, { getFirebase, getFirestore }) => {
        // make async call to database
        const firestore = getFirestore();　
        const profile = getState().firebase.profile;
        const authorId = getState().firebase.auth.uid;
        firestore.collection('projects').add({
            ...project,
            authorFirstName: profile.firstName,
            authorLastName: profile.lastName,
            authorId: authorId,
            createdAt: new Date()
        }).then(() => {
            dispatch({ type: 'CREATE_PROJECT', project})
        }).catch((err) => {
            dispatch({ type: 'CREATE_PROJECT_ERROR', err })
        })
        
    }
};
```

これで適切な作成されるプロジェクトに適切なユーザー情報が記述されるはず．プロジェクトを作ってみましょう．
<img width="1680" alt="スクリーンショット 2019-10-27 0.06.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/01cc7732-ee36-6dfd-7ebb-f925d6351a78.png">
<img width="1680" alt="スクリーンショット 2019-10-27 0.07.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/034ff417-0fb7-6c65-3d14-648d85fd879b.png">
<img width="1680" alt="スクリーンショット 2019-10-27 0.09.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/d201a0cf-7b7f-931c-f10a-f2f1dda78d85.png">
ダッシュボードにもちゃんと追加されたのを確認したのも束の間，問題に気づいただろうか．
`Posted by ~~`のところもハードコーディングだったので適切な表示ができていない．（ちなみに`ProjectDetail`の方は前に修正したのでちゃんと適切な名前が表示されていると思う）

`ProjectSummary`にて修正していこう．

```projects/ProjectSummary.js
import React from 'react'

const ProjectSummary = ({project}) => {
    return (
        <div className="card z-depth-0 project-summary">
            <div className="card-content grey-text text-darken-3">
                <span className="card-title">{project.title}</span>
                <p>Posted by the {project.authorFirstName} {project.authorLastName}</p>
                <p className="grey-text">3rd September</p>
            </div>
        </div>
    )
}

export default ProjectSummary;
```
<img width="1680" alt="スクリーンショット 2019-10-27 0.17.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331294/2193d2e1-8595-5d1f-1207-68dd0fcd40a1.png">

次はプロジェクトを作成したらダッシュボード画面にリダイレクトするようにしよう．

`CreateProject`をいじる．
`App.js`で，`react-router-dom`の`Rote`タグを使ってルート管理をしているわけだが，そのルーティングの対象のコンポネントは内部で`props`に`react-router-dom`の機能が入っていてその1つである`this.props.history.push()`が使える．

これにより画面の遷移が可能になる．

```projects/CreateProject.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createProject } from '../../store/actions/projectActions'
import { Redirect } from 'react-router-dom'

class CreateProject extends Component {
    state = {
        title: '',
        content: ''
    }
    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        this.props.createProject(this.state)
        this.props.history.push('/')
    }
    render() {
        const { auth } = this.props;
        if (!auth.uid) return <Redirect to='/signin' />

        return (
            <div className="container">
                <form onSubmit={this.handleSubmit} className="white">
                    <h5 className="grey-text text-darken-3">Create new project</h5>
                    <div className="input-field">
                        <label htmlFor="title">Title</label>
                        <input type="text" id="title" onChange={this.handleChange} />
                    </div>
                    <div className="input-field">
                        <label htmlFor="content">Project Content</label>
                        <textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
                    </div>
                    <div className="input-field">
                        <button className="btn pink lighten-1 z-depth-0">Create</button>
                    </div>
                </form>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        createProject: (project) => dispatch(createProject(project))
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(CreateProject)
```

適当にプロジェクトを作ってみてダッシュボード画面に遷移するかどうかと，ダッシュボードに今作ったプロジェクトは表示されているか確認しよう．

今回はここまで．

次回からは細かい調整と，いよいよ`Cloud Function`に触れていく．`そしてNotifications`も実装して完成まで持っていく．