Firebae Authを使ってReactでユーザーのログイン状態の反映をしようとした際のメモ

Firebae Authを使ってReactでユーザーのログイン状態を反映しようとしたら、どうやればReactに反映できるのか悩んだので、その時のメモ。

# 対象ユーザー
- Firebae Authのドキュメントを読んで何となく理解している。
- Reactの基本を理解している。
- Firebae Authを使ってReactでユーザーのログイン状態を連携させたいがやり方がわからない。

# 前提条件
- ReactでFirebase Authのログインが可能な状態。

# アウトプット
Firebae Authを使ってReactでユーザーのログイン状態を連携できるようになる。

# いざ実践

## いきなりで恐縮ですが・・・(;^_^A 
私は一からいろいろ試してゴールしたわけですが、終わってからQiitaで検索するとずばりの記事ありました(;^ω^)
https://qiita.com/HorieH/items/8b4b1c4807e1eb6fcb0a
非常にシンプルできれいだと思います。

・・・ここで終わるわけにはいかず・・・私がやりたいのは更にその先に・・・ある・・・(-"-;A ...アセアセ

 
## 各コンポーネントからUserオブジェクトを無理なく参照したい
私がやりたかったのは、Userオブジェクトのようなグローバル変数をバケツリレーすることなく、各コンポーネントで参照したかったのです。
reduxを導入してstoreに入れるのではなく、reactだけで完結したい・・・できます(￣▽￣)
ReactにはContextAPIというものがあり、多くのコンポーネントが参照する値を入れるように設計されています。
https://reactjs.org/docs/context.html
↑にUserオブジェクトの例がありますが、ほぼそのまま実装します(;^_^A

QrcodeのプロジェクトなのでIndexコンポーネントがQrcodeコンポーネントを子に持っています。
IndexコンポーネントはルートコンポーネントなのでstateでFirebaseのUserオブジェクトを管理しています。
そして、UserContext.Providerの値にstateを入れておくことで、ログイン状態が変化した際に配下のコンポーネントに伝搬します。
そのログイン状態を使うコンポーネント（ここではQrcode）では、React.createContext()で作成されたコンテキストをcontextTypeに設定すると、this.contextに値が入って来るようです。

Contextじゃないけど少しはまったのが、firebase.auth().onAuthStateChanged()のコールバック関数がドキュメントではfunction(){}だけど、thisでクラスメソッドつかうなら()=>{}にしないと、tihsの内容が異なるのでundefinedでコールバックが実行されなかったところです。

ソースがフルではない、Matarial-UI使用している、StyledFirebaseAuth使用している等々、メモなのでサンプルの適正低いですが誰かのお役に立てれば幸い。


```javascript:src/pages/index.js
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import withRoot from '../withRoot';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import firebase, { uiConfig } from '../configs/firebase'

import { UserContext } from '../contexts/user'
import Qrcode from '../components/qrcode';

const styles = theme => ({
  // ...
});

class Index extends React.Component {
  constructor(props) {
    super(props);
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // User is signed in.
        console.log("★★ User is signed in:", user);
        this.handleLogin(user);
      } else {
        // User is signed out.
        console.log("★★ User is signed out.");
        this.handleLogout();
      }
    });
  }

  state = {};

  handleLogin = (user) => { this.setState({ user }); }
  handleLogout = () => { this.setState({ user: null }); }

  render() {
    const { classes } = this.props;

    return (
      <UserContext.Provider value={this.state.user}>
        <div className={classes.root}>
          {
            this.state.auth || <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={firebase.auth()} />
          }
          <Qrcode />
        </div>
      </UserContext.Provider>
    );
  }
}

export default withRoot(withStyles(styles)(Index));
```

```javascript:src/components/qrcode.js
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import withRoot from '../withRoot';

import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';

import { UserContext } from '../contexts/user'


const styles = theme => ({
  // ...
});

class Qrcode extends React.Component {
  state = {};

  handleInputText = event => {
    console.log('text:', event.target.value);
    this.setState({
      text: event.target.value,
    });
  };

  render() {
    const { classes } = this.props;
    const { text } = this.state;

    const user = this.context;
    const signed = !!user;

    return (
      <>
        {
          <Paper className={classes.paper} elevation={1}>
            <Typography variant="h5" component="h3">
              QRCode Generator.
            </Typography>
            <TextField className={classes.form} label={signed ? "文字を入力" : "ログインしてください"} onChange={this.handleInputText} value={text} disabled={!signed} />
          </Paper>
        }
      </>
    );
  }
}

Qrcode.contextType = UserContext;

export default withRoot(withStyles(styles)(Qrcode));

```

```javascript:src/contexts/user.js
import React from 'react';

const user = null;

export const UserContext = React.createContext(
    user // default value
);

export default UserContext; 
```


# おまけ ～不勉強だとこんな感じで実装しちゃう～

firebase.auth().onAuthStateChanged()のコールバックでreactのsetState()がundefinedだっため、苦悩の末たどり着いたコード。
イベントを仲介して受け取らないと・・・と思ってカスタムイベントを採用する！
https://developer.mozilla.org/ja/docs/Web/Guide/Events/Creating_and_triggering_events

document.getElementById('root')でRootのDOMつかえばいいかなと（適当）。
ReactのコンストラクタでaddEventListener()しちゃえばいいかなと（適当）。

いつかカスタムイベント使う時が来た時のためのメモなので華麗にスルーでOKです(;´Д｀)

```javascript:src/pages/index.js
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import withRoot from '../withRoot';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import firebase, { uiConfig } from '../configs/firebase'

import { UserContext } from '../contexts/user'

const rootElement = document.getElementById('root');
firebase.auth().onAuthStateChanged(function (user) {
  if (user) {
    // User is signed in.
    console.log("★★ User is signed in:", user);
    rootElement.dispatchEvent(new CustomEvent('login', { detail: user }));
  } else {
    // User is signed out.
    console.log("★★ User is signed out.");
  }
});

const styles = theme => ({
  root: {
    // ...
  },
});

class Index extends React.Component {
  constructor(props) {
    super(props);
    rootElement.addEventListener('login', (e) => {
      this.handleLogin(e.detail);
    }, false);
  }

  state = {};

  handleLogin = (user) => { this.setState({ user }); }

  render() {
    const { classes } = this.props;

    return (
      <UserContext.Provider value={this.state.user}>
        <div className={classes.root}>
          {
            this.state.auth || <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={firebase.auth()} />
          }
        </div>
      </UserContext.Provider>
    );
  }
}

export default withRoot(withStyles(styles)(Index));
```

# 次回予告

ReactのContextを使ってデータの共有は達成しましたが、すぐに次の課題が出てきます。
Contextを複数参照するようなコンポーネント、そしてそれがいくつもある場合どうすればいいのか？
子からContextを更新したい場合にどうすればいいのか？
どれも公式Documentに書かれていますが、HOCをつかってちょっとだけかっこつける予定(;^_^A