Reactで作ったアプリにFirebase Authenticationでログインを実装

結論から言うと、index.jsでonAuthStateChangedを実行して、コールバックが返ってきてからReactDOM.renderを実行です(詳細は後述)。

はじめはcomponentWillMountでfirebaseのonAuthStateChangedを実行しようと思いました。

調べてみると、そのようにしている情報ばかりが出てきます。

しかし、少し試してみると、問題に気づきました。認証されてコールバックが返ってくるのがReact Componentのライフサイクルが終わってからになってしまうのです。

設計がFluxになっている場合、これは大きな問題になります。

Fluxの場合、Storeが更新されたらViewも更新するという流れなのですが、React Componentのライフサイクルの後に認証情報が返ってきてしまうと、その認証情報でStoreを更新した後に、もう一度React Componentのライフサイクルが始まるということになります。

componentWillMountやcomponentWillReceivePropsでたいした処理をしていないのなら、ちょっと無駄なことをしてるけどまあいいかで済みますが、大きな容量のデータを取得する処理を入れていれば、無駄なデータ転送をした上に、(目で見て分かるレベルで)表示の遅延も起きます。

さらに、認証が通っていないと表示してはいけないページがあった場合にどうしようか悩みます。ちょっと微妙ですが、認証情報がpropsに渡ってこなかったら、一旦render() { return null; }としておいて、認証情報が渡ってきてから表示するという手があります。しかし、認証が通っていなかったらリダイレクトさせたいという場合に困ってしまいます。また、そのページのcomponentWillMountやcomponentWillReceivePropsが認証が通っていなくても実行されてしまうのも良い気がしないですね。

キレイにFluxで設計しきることを諦めて、認証情報とそれに関する処理だけ別で実装することで解決することも可能かもしれませんが、そういうイレギュラーなことをするとバグの元凶になり得ますし、メンテナンス性が悪化してしまいます。

そこで、そもそもReactDOM.renderするのを、認証が通った後にすれば良いという結論に至りました。

firebaseに関しては、

/src/services/firebase.js

import * as firebase from 'firebase';


if (!firebase.apps.length) {
  const config = {
    apiKey: API_KEY,
    authDomain: AUTH_DOMAIN
  };

  firebase.initializeApp(config);
}

const auth = firebase.auth();

export {
  auth
};

こんな感じで準備をしておき、

/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import { auth } from './services/firebase';


auth.onAuthStateChanged((authUser) => {
  // ここで
  // store.dispatch(setAuthUser(authUser));
  // のように、storeの認証情報を更新する

  ReactDOM.render((
      // ここにルートとなるcomponentを書く
  ), document.getElementById('root'));
});

とすれば、storeに認証情報が保持されてからReact Componentのライフサイクルが始まるので問題は起きません。

認証が通っていないと表示してはいけないページは、componentWillMountでstoreを参照し、認証情報がなければログインページにリダイレクトすればいいだけです。