1時間でリアルタイムチャットを作る（React + FireBase + Material UI）

React + Firebase + Material UIを用いて短時間でリアルタイムチャットを開発する手順をまとめました。短時間でのサンプル開発のため、今回はシンプルに投稿ができる要素のみになります。

完成サンプル：
<img width="373" alt="スクリーンショット 2018-02-28 4.12.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/59118/970b7136-4555-871e-64fa-dbc59fe5003f.png">

# 準備
## node.js、npmのインストール

```terminal
brew install nodebrew
mkdir -p ~/.nodebrew/src
nodebrew install-binary latest

# 最新バージョンを確認（今回はv9.5.0であることを確認）
nodebrew list

# nodejsを有効にする
nodebrew v9.5.0

# コマンドのパスを通す
echo 'export PATH=$PATH:/Users/ユーザ名/.nodebrew/current/bin' >> ~/.bashrc

# bash_profileをtouch
touch .bash_profile

```

```~/.bashrc

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi
```

```terminal
source ~/.bashrc

# npm、nodeコマンドがたたけるかを確認
node -v
npm -v

```

## IDEの用意

今回は、Atomを使用する事にしました。

参考）
Atom : https://atom.io/
Webstrom : https://www.jetbrains.com/webstorm/

# React
## Reactプロジェクト作成

参考）
https://reactjs.org/tutorial/tutorial.html

```terminal

mkdir {指定ディレクトリ}
cd {指定ディレクトリ}

# React tutorialプロジェクトを作成
npm install -g create-react-app
create-react-app tutorial

```

## テスト実行
```terminal
# http:://localhost:3000/に環境ができていることを確認
npm start
```

# FireBase

## コンソールを開き、プロジェクトを作成

https://console.firebase.google.com/u/0/?hl=ja
※無料版は、プロジェクト数が5つまでと制限されているが、申請すれば上限を無料で増やしてくれるらしい。

<img width="710" alt="スクリーンショット 2018-02-28 2.49.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/59118/15e44548-d15a-a629-312b-4043c6116b2c.png">

## Firebaseの設定

作成したプロジェクトを開き、Authenticationページへ
<img width="1178" alt="スクリーンショット 2018-02-28 2.50.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/59118/a47bd830-2857-f695-b11a-96ad09d010f9.png">

右上にある「ウェブ設定」をクリックし、設定情報をコピーします。
これらの値が今回使用するFirebase設定情報になります。

tutorialプロジェクト内に戻り、src/firebase/config.jsを用意し、上記で取得した設定情報を入力し保存します。
以下の文献もとても参考になりました。
https://qiita.com/kazushikawamura/items/58ea222b3cc289882d79

```src/config/config.js
export const firebaseConfig = {
  apiKey: "***************",
  authDomain: "***************",
  databaseURL: "https://***************",
  projectId: "***************",
  storageBucket: "***************",
  messagingSenderId: "***************"
};
```

src/firebase/index.jsを用意し、Firebase Databaseを使用するための初期化処理を行う設定を書きます。

```src/config/index.js
import firebase from 'firebase';
import { firebaseConfig } from './config.js';

export const firebaseApp = firebase.initializeApp(firebaseConfig);
export const firebaseDb = firebaseApp.database();
```

## Firebaseへデプロイ

```terminal
npm install -g firebase-tools
firebase init
firebase login
```

firebase.jsonを以下のように設定します。

```firebase.json
{
  "database": {
    "rules": "database.rules.json"
  },
  "hosting": {
    "public": "build"
  }
}
```

database.rules.jsonを以下のように設定します。

```database.rules.json
{
  "rules": {
    ".read": "true",
    ".write": "true"
  }
}
```

Firebase Databaseへのデプロイを実行します。

```terminal
npm run build
firebase deploy
```

# Material UI

## Material UIのインストール

参考）
https://www.npmjs.com/package/material-ui

```terminal
npm install material-ui
```

## cssを用意する

Reactプロジェクト内の構造を整理するため、今回はデフォルトで用意されているApp.cssなどをsrc/cssに移動しました。
中身については、Material UIを用いるので特にデフォルトのままで利用します。

## Componentを作成

再び参考）
https://qiita.com/kazushikawamura/items/58ea222b3cc289882d79

### src/components/ChatBox.jsを作成。

チャットの投稿フォームのUIが実装されています。
Materila UIのパーツを利用するには、MuiThemeProviderをimportし、
<MuiThemeProvider>タグで囲うことで、その中で各種コンポーネントを使用することが出来ます。
http://www.material-ui.com/#/get-started/usage

Material UIのRaisedButton、TextFieldパーツを用いています。
http://www.material-ui.com/#/components/raised-button
http://www.material-ui.com/#/components/text-field


```src/components/ChatBox.js
import React from "react";
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';

export default class ChatBox extends React.Component {
  render() {
    return (
      <MuiThemeProvider>
        <div className="ChatBox">
          <div className="">
            <TextField name='user_name' onChange={this.props.onTextChange} className=""  placeholder="Name" />
<br />
            <TextField name='profile_image' onChange={this.props.onTextChange} className="" placeholder="Profile Image URL" />
          </div>
          <TextField rows="4" multiLine="true" name='text' className="" onChange={this.props.onTextChange} />
          <RaisedButton primary="true" label="Send" className="" onClick={this.props.onButtonClick} />
        </div>
      </MuiThemeProvider>
    );
  }
}

```

### src/components/Message.jsを作成。

投稿されたチャット内容のUIを実装します。

Material UIのAvatar、List、ListItem、Chipパーツを用いています。
http://www.material-ui.com/#/components/avatar
http://www.material-ui.com/#/components/list
http://www.material-ui.com/#/components/chip

```src/components/Message.js
import React from "react";
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Avatar from 'material-ui/Avatar';
import List from 'material-ui/List/List';
import ListItem from 'material-ui/List/ListItem';
import Chip from 'material-ui/Chip';

const styles = {
  chip: {
    margin: 4,
  },
  wrapper: {
    display: 'flex',
    flexWrap: 'wrap',
  },
};

const style = {margintop: -5};

export default class Message extends React.Component {
  render() {
    return (
      <div className="Message">
      <List>
        <ListItem disabled="true">
          <Avatar className="" src={this.props.message.profile_image} />
          <span style={{marginBottom: -5}}>@{this.props.message.user_name}</span>
          <div className="">
            <Chip style={styles.chip} >
              {this.props.message.text}
            </Chip>
          </div>
        </ListItem>
      </List>
      </div>
    );
  }
}

```

### src/components/AppChat.jsを作成。

チャット全体のUIを構成します。
上記で用意したChatBox、Messageを用います。
また、onTextChange()、onButtonClick()を用意して、各種アクションを実装します。

```src/components/AppChat.js
import React, { Component } from 'react';
import './../css/App.css';
import { firebaseDb } from './../firebase/index.js'
import Message from './Message.js'
import ChatBox from './ChatBox.js'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

const messagesRef = firebaseDb.ref('messages')

class AppChat extends Component {
  constructor(props) {
    super(props);
    this.onTextChange = this.onTextChange.bind(this)
    this.onButtonClick = this.onButtonClick.bind(this)
    this.state = {
      text : "",
      user_name: "",
      profile_image: "",
      messages : []
    }
  }

  render() {
    return (
      <MuiThemeProvider>
        <div className="App">
          <div className="App-header">
            <h2>Chat</h2>
          </div>
          <div className="MessageList">
            {this.state.messages.map((m, i) => {
              return <Message key={i} message={m} />
            })}
          </div>
          <ChatBox onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
        </div>
      </MuiThemeProvider>
    );
  }

  onTextChange(e) {
    if(e.target.name == 'user_name') {
      this.setState({
        "user_name": e.target.value,
      });
    } else if (e.target.name == 'profile_image') {
      this.setState({
        "profile_image": e.target.value,
      });
    } else if (e.target.name == 'text') {
      this.setState({
        "text": e.target.value,
      });
    }
  }

  onButtonClick() {
    if(this.state.user_name == "") {
      alert('user_name empty')
      return
    } else if(this.state.text == "") {
      alert('text empty')
      return
    }
    messagesRef.push({
      "user_name" : this.state.user_name,
      "profile_image" : this.state.profile_image,
      "text" : this.state.text,
    })
  }

  componentWillMount() {
    messagesRef.on('child_added', (snapshot) => {
      const m = snapshot.val()
      let msgs = this.state.messages

      msgs.push({
        'text' : m.text,
        'user_name' : m.user_name,
        'profile_image' : m.profile_image,
      })

      this.setState({
        messages : msgs
      });
    })
  }

}

export default AppChat;

```

### src/index.jsを編集し、上記AppChat.jsを呼び出すようにする

```src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './css/index.css';
import registerServiceWorker from './registerServiceWorker';
import AppChat from './components/AppChat';
import injectTapEventPlugin from 'react-tap-event-plugin';

ReactDOM.render(<AppChat />, document.getElementById('root'));
registerServiceWorker();

```

##　チャットアプリを実行

```terminal
npm start
```

# 感想・まとめ

- Reactを今回はじめて触れていきましたが、これまでのHTML、jsの書き方とは若干異なるためコーディングを覚えていく必要があると感じた。
- 今回は特に触れていないが、Reduxを用いるものになってくるとより複雑な状態遷移のアプリケーションも開発することができるとのことなので、今後触れていきたい。
- Firebaseは、思惑通りリアルタイムチャットを用意に実現してくれたので本件については非常に満足したが、細かい用途に対してどこまでカスタマイズしていけるかは引き続き要調査。
- Material UIは、bootstrapのようなシンプルでスッキリしたUIを簡単に用意することができる。また、カスタマイズに関しても柔軟なようなので今後もReactプロジェクトでは活用していきたい。
- 今回のチャットサンプルの実装でも、他Qiita文献には大変お世話になりました。感謝。
- 実際１時間で作れたかと言うと、間に別件対応なども入ってしまったため、実際には3時間程度かかってしまいました。それらがなくストレートにいければ、この手順を見ながら１時間以内で作っていけるかと思います。