【React】Firebaseの認証状態チェックコードはどこに書くべきなのか

# まえがき
**React/React Native + Redux**でのアプリケーション開発において、Firebaseの認証状態チェックコードは、どこに書くべきなのか。

適当なコンポーネントのライフサイクルの`componentDidMount`に書く。
→　これが一番よく目にする書き方

しかし、この書き方だと認証状態チェックコードが書かれたコンポーネントは常に表示しておかなければいけなかったり、ルーティングライブラリ(**react-router**, **react-navigation**)を導入した時、さらなる混沌が待っている。

# 目的

Firebaseの認証状態チェックコードを「開発状況」や「アプリケーションの構造」を考慮した上で、書く(べき)場所を定める。

# 対象者
React/React Nativeのアプリケーション開発で、Firebase Authenticationを使っている人
Firebaseの認証状態チェックのコードをコンポーネントに書いているがしっくりきていない人
redux-sagaを知っている人

※_最後のセクションでredux-sagaが出てきます。_

# 本論(書き方)

### 1. 「適当なコンポーネント」に書く

ヘッダー等、常に表示させる「**適当なコンポーネント**」の`componentDidMount`に書く。

```jsx
import React from 'react'
import * as firebase from 'firebase'

// firebase.initializeApp(config)

class MyComponent extends React.Component {
  componentDidMount() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.props.handleStatusOk() // コンテナーで定義した関数
      }
      else {
        this.props.handleStatusNg() // コンテナーで定義した関数
      }
    })
  }

  render() {
    // 省略
  }
}

export default MyComponent
```

#### 考察

この書き方は、**MyComponentが常に表示されるのであれば問題ない**。
MyComponentが表示されない場面があるのであれば、その場面において認証状態のチェックは行われないので注意が必要。

常に表示させる「**適当なコンポーネント**」がないのであれば、**他のコンポーネントのcomponentDidMountにも記載する等工夫が必要**になる。

「**適当なコンポーネント**」はコンテナーでreact-reduxのconnectでラップされているはずなので、認証状態が変化したタイミングでアクションをディスパッチする関数を簡単に実行させることができる。

#### 結論

「**アプリケーションの構造があらかた固まっている、かつ常に表示させるコンポーネントがある場合**」のみこの書き方はあり。
構造が固まっていないのであれば、常に表示させる予定だったコンポーネントが表示されない場面がでてくる可能性も考え、おすすめしない。

※_独断と偏見が入り混じった結論です、ご了承ください。_

### 2. 各コンポーネントを束ねた「親コンポーネント」に書く

アプリケーションで利用する各コンポーネントを全て束ねた「**親コンポーネント**」を用意し、**1. 適当なコンポーネントに書く**と同じ書き方で書く。

`create-react-app`でWebアプリケーションを作成したときにできる**App.js**のことである。

```jsx
import React from 'react'
import { connect } from 'react-redux'
import * as firebase from 'firebase'

// firebase.initializeApp(config)

class App extends React.Component {
  componentDidMount() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // 直接アクションをディスパッチ
        this.props.dispatch({ type: "STATUS_OK" })
      }
      else {
        // 直接アクションをディスパッチ
        this.props.dispatch({ type: "STATUS_NG" })
      }
    })
  }

  render() {
    // 省略
  }
}

const mapStateToProps = (state) {
  return { state }
}

export default connect(mapStateToProps)(App)
```

#### 考察

この書き方は、「**親コンポーネント**」を通して各コンポーネントが表示されるので、あまり構造を意識せず、常に認証状態をチェックをさせることができる。

ただし、認証状態が変化したタイミングでアクションをディスパッチする場合、各コンポーネントだけでなく、「**親コンポーネント**」も**react-reduxのconnectでラップする必要**が出てくる。

例では直接アクションをディスパッチしているが、もちろん関数化して実行させてもいい。


#### 結論

「**常に表示させるコンポーネントがない場合**」、「**アプリケーションの構造が固まっていない場合**」この書き方はあり。

認証状態をチェックさせるためだけに各コンポーネント束ねる「**親コンポーネント**」を作りたくない、「**親コンポーネント**」はreact-reduxのconnectでラップしたくないなど、この書き方にはしっくりこない人がいるかもしれない。
→　自分は親コンポーネントをラップするところが気に食わなかった。

※独断と偏見が入り混じった結論です、ご了承ください。

### 3. redux-sagaを利用することで「タスク」に書く

「**タスク**」に**eventChannel**を利用して書く。

```js
import { take, put, all } from 'redux-saga/effects'
import { eventChannel } from 'redux-saga'
import * as firebase from 'firebase'

// firebase.initializeApp(config)

const data = (type ,payload) => {
  const _data = {
    type: type,
    payload: payload,
  }

  return _data
}

const authChannel = () => {
  const channel = eventChannel(emit => {
    const unsubscribe = firebase.auth().onAuthStateChanged(
      user => emit({ user }),
      error => emit({ error })
    )
    return unsubscribe
  })
  return channel
}

function* checkUserStateSaga() {
  const channel = yield call(authChannel)
  while (true) {
    const { user, error } = yield take(channel)

    if ( user && !error ) {
      yield put(data('REDUCER_SET_UID', user.uid))
      yield put(data('REDUCER_GET_PROFILE_REQUEST', null))
    }
    else {
      yield put(data('REDUCER_SET_UID', null))
    }
  }
}

export default function* rootSaga() {
  yield all([
    checkUserStateSaga(),
  ])
}
```

#### redux-sagaとは

**redux-saga**を導入することで、「**タスク**」という概念をReduxに導入することができます。
「**タスク**」はプロセスのようなもので独立して、並列で動きます。

ここら辺の記事が参考になります。
**redux-saga**を知らないかたは先読んで理解しておくことをおすすめします。

[redux-sagaで非同期処理と戦う](https://qiita.com/kuy/items/716affc808ebb3e1e8ac)
[Redux-Sagaでテトリス風ゲームを実装して学んだこと](https://qiita.com/uehaj/items/eaa039a7ce44d5969f06)
[Redux SagaのeventChannelを使ってみる](https://blog.mismithportfolio.com/web/redux-saga-eventchannel)

#### 考察

この書き方は、認証状態チェックコードを「**タスク**」に書いているため、独立して認証状態のチェックが行われる。これにより**ビューとロジックの分割が可能**になり、認証状態をチェックすることを気にせずにコンポーネントを書くことができる。

ただし、**redux-saga**を導入しなければいけないため、**別ライブラリ導入と学習コストの弊害**が出てくる。

※_弊害と書いてますが、学習すればいいだけの話なんで知らない人は学習しちゃいましょう。_

#### 結論

「**認証状態チェックを独立して実行させたい場合**」、「**常に表示させるコンポーネントがない場合**」、「**アプリケーションの構造が固まっていない場合**」この書き方はあり。

※_「認証状態チェックを独立して実行させたい場合」は「ビューとロジックを分割させたい場合」と同義_

コンポーネントで認証状態のチェックを気にせず、ビューに特化した形で開発できるので、個人的には**redux-saga**を導入するメリットでかいと思っています。

しかし、認証状態をチェックさせるためだけに「**タスク**」という概念を導入したくない、Redux onlyのシンプルな構造のまま開発したい等、この書き方には反対の方も結構いるかもしれません。

※独断と偏見が入り混じった結論です、ご了承ください。

# まとめ

常に表示するコンポーネントがあるのであれば、そのコンポーネントに書く。
常に表示するコンポーネントがないのであれば、redux-sagaを利用して「タスク」に書く。
redux-sagaを利用したくないのであれば、親コンポーネントに書く。