React (Firebase, Redux)の構成でアプリを作成しました【Firebaseを使ったチャットアプリ】

## Reactアプリの作成

```
$ create-react-app <アプリ名>
```

## 必要なパッケージのインストール

- redux
- react-redux
- @material-ui/core
- @material-ui/icons
- firebase

```
$ yarn add redux react-redux @material-ui/core @material-ui/icons firebase
```

## Firebaseの設定

公式サイト：[Firebase](https://firebase.google.com/?hl=ja)


① Firebaseにアクセスし、アカウントでログインし、コンソール画面に移動する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/62307eec-ba2c-f4e4-588b-a1d5a1821ce2.png)

②プロジェクトの追加を押して、プロジェクト名を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/83db2876-2a91-1162-12c7-3d09a9681f67.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/c994aed9-c303-6933-1457-214936a2d86a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/1ec8d7af-1a66-7b4a-c366-130294dc9209.png)


③ウェブアプリを追加するために、ニックネームを入力する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/717a4bda-d044-d946-543b-9da0c76a80d7.png)

## Firebase Realtime Databaseとの紐付け
firebase/config.jsを作成し、Firebaseとの接続情報を記載する。

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

firebase/index.jsでデータベース参照用のインスタンスを作成する。
このファイルをインポートすることで、どこからでもデータベースにアクセスすることができる。

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

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

## ComponentをMaterial-UIから探す

公式：[Material-UI]
(Material-UI)

[ヘッダー]
(https://mui.com/components/app-bar/#app-bar)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/3498849e-4f99-7aac-9067-8deb7355c60d.png)

[メッセージ入力欄]
(https://mui.com/components/text-fields/#textfield)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/13c95700-4415-1460-d29e-f282dbab2944.png)

[メッセージ送信ボタン]
(https://mui.com/components/buttons/#buttons)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/1dbfe0fc-a452-cc10-3215-7e6e9f89f0b5.png)


[メッセージ表示欄]
(https://mui.com/components/lists/#lists)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/667b1402-fb0d-9caf-b95d-f99793b201b2.png)

##Material-UIのComponentを編集する
####ページ全体

```src/App.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import AlignItemsList from './components/AlignItemsList';
import ButtonAppBar from './components/ButtonAppBar.js';
import TextInput from './components/TextInput.js';
import {firebaseDb} from './firebase';
import './styles/App.css';

const messagesRef = firebaseDb.ref('messages');

class App extends Component {
  render() {
    return (
      <React.Fragment>
        <ButtonAppBar />
        <div className="App">
          <AlignItemsList />
          <TextInput value="メッセージを入力" />
        </div>
      </React.Fragment>
    );
  }
}

export default App;
```
```src/styles/App.css
.App{
  margin: 0 auto;
  max-width: 500px;
}

@media screen and (max-width:520px) { 
  /*　画面サイズが520pxからはここを読み込む　*/
  .App{
    margin: 0 20px;
  }
}

.App ul {
  margin: 10px auto;
}

.App ul li {
  padding-left: 0;
  display: flex;
  align-items: center;
}

.App ul .MuiDivider-inset{
  margin-left: 57px;
}

.App ul li .MuiListItemAvatar-alignItemsFlexStart {
  margin-top: 0;
}
```

#### ヘッダー

```src/components/ButtonAppBar.js
import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import '../styles/ButtonAppBar.css';

const useStyles = makeStyles(theme => ({
  root: {
    flexGrow: 1,
  },
  menuButton: {
    marginRight: theme.spacing(2),
  },
  title: {
    flexGrow: 1,
  },
}));

function ButtonAppBar() {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <AppBar position="static">
        <Toolbar>
          <IconButton
            edge="start"
            className={classes.menuButton}
            color="inherit"
            aria-label="Menu">
            <MenuIcon />
          </IconButton>
          <Typography variant="h4" className={classes.title}>
            PigTalk
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </div>
  );
}

export default ButtonAppBar;
```

```src/styles/ButtonAppBar.css
h4 {
    text-align: center;
}
```

#### メッセージ入力欄

```src/components/TextInput.js
import React, {Component} from 'react';

import TextField from '@material-ui/core/TextField';
import '../styles/TextInput.css';
import SendButtons from './SendButton';

import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

class TextInput extends Component {
  render() {
    return (
      <React.Fragment>
        <form className="wrap" noValidate autoComplete="off">
          <TextField
            id="standard-text"
            label="メッセージを入力"
            className="text"
            margin="normal"
          />
          <SendButtons />
        </form>
      </React.Fragment>
    );
  }
}
export default TextInput;
```
```src/styles/TextInput.css
.wrap {
    display: flex;
    justify-content: center;
}

.wrap .text {
    width: 80%;
}

.wrap button {
    margin-top: 24px;
    margin-right: 0;
    width: 20%;
}
```

#### メッセージ送信ボタン

```src/components/SendButton.js
import React from 'react';

import Button from '@material-ui/core/Button';
import {makeStyles} from '@material-ui/core/styles';
import Icon from '@material-ui/core/Icon';

const useStyles = makeStyles(theme => ({
  button: {
    margin: theme.spacing(1),
  },
  leftIcon: {
    marginRight: theme.spacing(1),
  },
  iconSmall: {
    fontSize: 20,
  },
}));

function SendButtons() {
  const classes = useStyles();

  return (
    <Button variant="contained" color="primary" className={classes.button}>
      <Icon>send</Icon>
    </Button>
  );
}
export default SendButtons;
```

#### メッセージ表示欄

```src/components/AlignItemsList.js
import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import Divider from '@material-ui/core/Divider';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';

const useStyles = makeStyles(theme => ({
  root: {
    backgroundColor: theme.palette.background.paper,
  },
  inline: {
    display: 'inline',
  },
}));

function AlignItemsList() {
  const classes = useStyles();

  return (
    <List className={classes.root}>
      <ListItem alignItems="flex-start">
        <ListItemAvatar>
          <Avatar
            alt="Remy Sharp"
            src="https://hellogiggles.com/wp-content/uploads/2015/03/11/micro-pig-LondonPignic.jpg"
          />
        </ListItemAvatar>
        <ListItemText primary="Hello World" />
      </ListItem>
      <Divider variant="inset" component="li" />
    </List>
  );
}
export default AlignItemsList;
```

##送信ボタンのIconを使うために

```public/index.html
<head>
    ...
    <!-- Google Icon -->
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
    />
    ...
</head>
```

## 実装完了

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/1874326d-ced9-4d0f-8481-790bfb1e0144.png)

## redux-actions

redux-actionsとは、reduxでの状態管理のガイドライン(Flux Standard Action)を引き、その実装を助けてくれるライブラリのこと。

- actionの記述を楽にする『createAction(s)』
- reducerの記述を楽にする『handleAction(s)』
- 同じreducerの処理をする際に、複数のactionをまとめる『combineActions』
の３つがある。

## createAction(s)

createActionsを使って、まとめてアクション(クリエイター)を生成する。

```src/redux/actions/messages.js
import {createActions} from 'redux-actions';

import {firebaseDb} from '../../firebase';

const messagesRef = firebaseDb.ref('messages');

const {messages} = createActions({ 
  messages: { 
    submit(value, image) { 
      const payload = value; 
      if (payload === '') { 
        alert('メッセージを入力してください'); 
        return false; 
      }

      messagesRef.push({ 
        image: image, 
        text: payload, 
      }); 

      return payload; 
    }, 
    change(value) { 
      const payload = value; 
      return payload; 
    }, 
  }, 
}); 

export {messages}; 
```
『submit』は入力されたメッセージをfirebaseに送信する際に生成されるactionで、『change』はtextareaに文字が入力される度に生成されるactionである。

また messagesRef.pushにてfirebaseにデータを送信する。

##handleAction(s)

handleActionsを使って、まとめてreducerを定義する

```src/redux/reducers/messages.js
import {handleActions} from 'redux-actions';

import {actions} from '../actions';
import {repos} from '../repos'; 
import {initialState} from '../store/initialState';

const messages = handleActions( 
  {
    [`${actions.messages.submit}`](state, action) {
      return repos.messages.addMessage(state, action.payload);
    }, 
    [`${actions.messages.change}`](state, action) { 
      return repos.messages.changeText(state, action.payload); 
    }, 
  }, 
  initialState 
); 

export {messages};
```
このファイルの中では状態遷移に関する記述はせず、以下のrepos/messages.jsにて『immer』を用いて状態遷移を行うようにする。

## immer

immerは現在のstateを渡すと下書き状態であるdraftが渡されそれに対して変更を行うと、変更が反映された新しいオブジェウトを返却してくれる。変更は通常のJSオブジェクトと同様に行うことができる。

```src/redux/repos/messages.js
import produce from 'immer';

export const addMessage = (state, payload) => {
  const newState = produce(state, draftState => {
    draftState.value = '';
    draftState.text = payload;   
  });

  return newState;
};

export const changeText = (state, payload) => { 
  const newState = produce(state, draftState => { 
    draftState.value = payload;
  }); 

  return newState;
};
```

## 参考サイト
[『React』 +『Redux』 + 『Firebase』でLINE風のChat機能を作ろう！ 【準備編】](https://qiita.com/micropig3402/items/5a24f35f68bb6b3c8e81)
[『React』 +『Redux』 + 『Firebase』でLINE風のChat機能を作ろう！ 【Component編】](https://qiita.com/micropig3402/items/3431c998df582a441fa5)
[『React』 +『Redux』 + 『Firebase』でLINE風のChat機能を作ろう！ 【状態管理編】](https://qiita.com/micropig3402/items/7f3c5e821a7e7d9a76d7)