Reactで作ったチャットのメッセージをFirebaseRealtimeDatabaseに追加、削除する

## はじめに
`React`と`Firebase`を使用したチャットシステムを作成していました。
それまでは`Firebase`はログイン状態を管理するために使用していましたが、`RealtimeDatabase`の操作を学びたかったため、メッセージの追加、削除も管理するようにしました。
その時に学んだことを記録します。

## この記事のコードでできること
<img src="https://qiita-image-store.s3.amazonaws.com/0/218398/49705f51-7b67-cce6-4f43-6969aee32d54.gif" width="700px">
削除ボタンを押した際、`更新してください`というポップアップが表示されます。


## 現在の状態

- URL: [Reactチャット](https://react-chat-b0e8a.firebaseapp.com/)
- Github: [kaibara/React-chat](https://github.com/kaibara/React-chat)

## 実装
### ファイル構造
今回のファイル構造はこのようになっています。

```
src ー App.js
　　 |ー components
　　 |     　|ー ChatMEssage.js
　　 |     　|ー ChatForm.js
 　　|
　　 |ー firebase
　　　　　　　|ー firebase.js

```

### FirebaseRealtimeDatabase
まずは`Firebase`の`RealtimeDatabase`に接続します。
アプリの名前の設定などができていない場合は、[ReactにFirebaseでGoogleアカウントの情報を反映させる](https://qiita.com/Takumi_Kaibara/items/2b30f86c9241dc597bf9)を参考にしてください。

#### Firebase console
`Firebase console`でアプリの名前が設定できたら、`開発 -> Database`を選択してください。
最初に`Cloud Firestore`が表示されますが、それではなく、その下にある`Realtime Database`を選びましょう。
<img src="https://user-images.githubusercontent.com/34569299/53355837-4f875b00-396d-11e9-98d4-8ae81906ffb4.png" width="500px">

次に、`データベースを作成`を選択し、`テストモード`を選んでください。
<img src="https://user-images.githubusercontent.com/34569299/53355936-88bfcb00-396d-11e9-91e0-19389683db4a.png" width="500px">

#### firebase init
ここまでできたら端末の操作に移ります。

```sh
 $ firebase init
 // Database,Hostingを選択
 // 基本的に全部Enterで可能、カスタマイズしたい場合は各自で変更
```

上のコマンドの処理が終わったら、作成された`database.rules.json`を確認してください。

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

このようになっていますか?
なっていなかったら、上のように変更してください

#### コードから接続
最後に、ファイル、コードから`FirebaseRealtimeDatabae`に接続できるようにします。
行うことは`firebase`の設定が記載されたファイルに`Database`に関連するコードを追加するだけです。
`config`で定義しているコードは[ReactにFirebaseを使ったログイン機能を実装する
](https://qiita.com/Takumi_Kaibara/items/6cfbcfed55cc6e817b20) を参考に持ってきてください。

```firebase/firebase.js
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
//追加するコード1つ目

const config = {
  apiKey: "***",
  authDomain: "***",
  databaseURL: "***",
  projectId: "***",
  storageBucket: "***",
  messagingSenderId: "***"
}

export const firebaseApp = firebase.initializeApp(config)
export const firebaseDB = firebase.database()
//追加するコード2つ目
```

このようなファイルを作成することで`firebase`と`RealtimeDatabase`を使用できるようになります。

### データの追加
続いて、データ、メッセージを`RealtimeDatabase`に追加できるようにします。
では、解説していきます。
#### ChatForm.js
ここではユーザー名及びメッセージを入力するフォームを作成しています。
`RealtimeDatabae`と直接の関わりはありません。
ここで入力された値が`RealtimeDatabase`に渡されるため、`src/App.js`で定義されたイベントと照らし合わせて確認してください。

```src/components/ChatForm.js
import React,{Component} from 'react'
import firebase from 'firebase/app'
import {firebaseApp} from '../firebase/firebase'

class ChatForm extends Component {
  render(){
    return(
      <div id='Form'>
        <input name='user_name' onChange={this.props.onTextChange} placeholder='名前'/>
        <textarea name='text' onChange={this.props.onTextChange}  placeholder='メッセージ'/>
        <button onClick={this.props.onButtonClick}>送信</button>
      </div>
    )
  }
}

export default ChatForm
```

#### ChatMessage.js 
ここでは今までのメッセージが表示されます。
メッセージのデータは`src/App.js`から`props`を使用して渡されます。
`RealtimeDatabase`のデータはここに表示されるとだけ覚えておいてください。

```ChatMessage.js
import React,{Component} from 'react'

class ChatMessage  extends Component {
  render(){
    return(
      <div>
        <p>{this.props.message.text}</p>
        <p>by&nbsp;{this.props.message.user_name}</p>
      </div>
    )
  }
}

export default ChatMessage
```

#### App.js
このファイルが`RealtimeDatabase`と直接やり取りをします。
まずはコードをご覧ください。

```src/App.js
import React, { Component } from 'react'
import firebase from 'firebase/app'
import { firebaseApp,firebaseDB } from './firebase/firebase'
import ChatMessage from './components/ChatMessage'
import ChatForm from './components/ChatForm'

const messagesRef = firebaseDB.ref('messages')
//RealtimeDatabaseの`messages`という要素にメッセージを保存する

class App extends Component {
  constructor(props) {
    super(props)
    this.onTextChange = this.onTextChange.bind(this)
    this.onButtonClick = this.onButtonClick.bind(this)
　　 //このファイルで使用するイベントを明確化している
    this.state = {
      text : "",
      user_name: "",
      messages : []
    }
  }

  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,
        })
        this.setState({ messages : msgs })
      })
    }
  //下で解説

  onTextChange(e) {
  // ChatForm.jsで入力された値がthis.state.user_nameとthis.state.textに格納される
    if(e.target.name == 'user_name') {
      this.setState({
        "user_name": e.target.value
      });
    } else if (e.target.name == 'text') {
      this.setState({
        "text": e.target.value
      });
    }
  }

  onButtonClick() {
  //ChatForm.jsの送信ボタンが押されたらthis.stateに格納された値をRealtimeDatabaseに送信する
    messagesRef.push({
      "user_name" : this.state.user_name,
      "text" : this.state.text
    })
    this.setState({"text": ""})
  }

  render() {
    return (
      <div>
        <div>
          <h2>メッセージログ</h2>
          {this.state.messages.map((m, i) => {
            return <ChatMessage key={i} message={m} />
          })}
          //それぞれのメッセージ毎にiというキーを割り当てて、RealtimeDatabaseにある全てのメッセージを表示する
        </div>
        <ChatForm onTextChange={this.onTextChange} onButtonClick={this.onButtonClick} />
　　　　　//ChatForm.jsにここで定義した2つのイベントを渡す
      </div>
    )
  }
}

export default App;
```

eventなどReactの基本的な内容がわかっていればそこまで難しくはないでしょう。
注目していただきたいのは`componentWillMount()`です。
ここで定義している内容によって、`RealtimeDatabae`に送ったメッセージが即座に表示されるようになっています。

では、コメントアウトで解説していきます。

```App.js
  componentWillMount() {
  //このファイルのコードが読み込まれる前に行う
      messagesRef.on('child_added', (snapshot) => {
      //RealtimeDatabseにデータが追加されたら以下の操作を行う
        const m = snapshot.val()
　　　　　//追加されたデータをmとして扱う
        let msgs = this.state.messages
        //データが追加される前のthis.state.messegesをmsgsに入れる
        msgs.push({
          'text' : m.text,
          'user_name' : m.user_name,
        })
        //msgsに追加されたデータを追加する
        this.setState({ messages : msgs })
　　　　　//最新のデータをこのコンポーネントで扱えるようmsgsをthis.state.messagesとして定義する
      })
    }
```

やっていることは`RealtimeDatabae`に追加されたメッセージを`this.state.messages`追加しているだけです。
わかりづらかったら`console.log()`を使って、一つずつのコードでどのようなデータが扱われているのかを確認してみてください。

### データの削除
続いて、データを削除するコードを実装していきます。
ここで扱うのは、`ChatMessage.js`と`App.js`です

#### App.js
`App.js`で変更するのは`componentWillMount()`の中の一箇所だけです。

```src/App.js
  componentWillMount() {
      messagesRef.on('child_added', (snapshot) => {
        const m = snapshot.val()
        let msgs = this.state.messages
        console.log({msgs})
        msgs.push({
          'text' : m.text,
          'user_name' : m.user_name,
          'key': snapshot.key
        })
        this.setState({ messages : msgs })
        console.log(this.state.messages)
      })
    }
```

データを削除する際に必要なのは、削除したいメッセージと`this.state.messages`で保存しているメッセージが一致するようにすることです。
`RealtimeDatabase`にデータが追加される際、`snapshot`によって一意のキーが割当られます。
そのキーを`this.state.messages`に保存することで、キーを呼び出せばそのデータを削除できるようになるのです。
実際に削除するのは`ChatMessage.js`で行います。

#### ChatMessage.js
`RealtimeDatabse`でデータの削除は`remove()`だけで行なえます。
そのためには削除したいデータを保存している場所、キーを見つけ出すことが必要ですが、`snapshot`のキーを`this.state.messages`に格納したことでメッセージ毎にデータを削除できるようにしました。

```src/componentsChatMessage.js
import React,{Component} from 'react'
import { firebaseDB } from '../firebase/firebase'

const messagesRef = firebaseDB.ref('messages')

class ChatMessage  extends Component {
  constructor(props) {
    super(props)
    this.onRemoveClick = this.onRemoveClick.bind(this)
  }
  onRemoveClick(){
     messagesRef.child(this.props.message.key).remove()
     //メッセージ毎にキーがあるためそれを取得して削除する
     alert('メッセージはページを更新した後に削除されます')
  }
  render(){
    return(
      <div>
        <p>{this.props.message.text}</p>
        <p">by&nbsp;{this.props.message.user_name}</p>
        <button onClick={this.onRemoveClick}>削除</button>
　　　　　//削除ボタンの実装
      </div>
    )
  }
}

export default ChatMessage
```

## まとめ
今回はページから`RealtimeDatabase`へデータの追加、削除する方法をご紹介しました。
削除機能に関して、データを削除してもスグに表示が変更されないため、それが今後の課題となっています。
自身で発見すれば更新しますが、もし良い方法が見つかった際は、コメントなどで教えていただけると助かります。


## 参考資料
[React & Firebaseで簡単なChatアプリを作ってみた](https://qiita.com/kazushikawamura/items/58ea222b3cc289882d79#firebase-realtime-database%E3%81%A8%E3%81%AE%E7%B4%90%E4%BB%98%E3%81%91)
[ウェブでのデータの読み取りと書き込み - Firebase Documentation](https://firebase.google.com/docs/database/web/read-and-write#updating_or_deleting_data)
[ウェブ上でデータリストを操作する - Firebase Documentation](https://firebase.google.com/docs/database/web/lists-of-data#listen_for_child_events)
[How to add remove data function of Firebase Realtime Database - stackoverflow](https://stackoverflow.com/questions/54854542/how-to-add-remove-data-function-of-firebase-realtime-database)
[How to read props on event on React - stackoverflow](https://stackoverflow.com/questions/54861345/how-to-read-props-on-event-on-react)