nodejs + mysql + React + ReduxでCRUDアプリを作る Part2

#概要
シンプルなCRUD(create, read, update, delete)アプリをデータベースはmysql, フロントエンドはReact + Reduxで作ってみます.
今回はReact + Reduxでフロントエンドを作ります.
Part1は[こちら](https://qiita.com/YuasaJunki/items/98691188459014c6ec7f)
なお、今回は[こちらの記事](https://qiita.com/muiscript/items/573247b12ff0bc4e5d3c)をほぼパクらせていただきました. ありがとうございます.

#セットアップ
まずcreate-react-appで雛形を作ります.

```
$ cd crud-node
$ npx create-react-app client
$ cd client
```
次に```package.json```を以下のように書き換えます.

```:package.json
{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.19.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-redux": "^7.0.3",
    "react-scripts": "3.0.1",
    "redux": "^4.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
```
で、```npm install```しましょう

```
$ npm install
```
#reducer
reducerはこのような感じになります.

```client/src/reducers/index.js
import { combineReducers } from 'redux'
import {
    CHANGE_NAME, CHANGE_STATUS, INITIALIZE_FORM,
    REQUEST_DATA, RECEIVE_DATA_SUCCESS, RECEIVE_DATA_FAILED
} from '../actions';

const initialState = {
    form: {
        name: '',
        status: '',
        ustatus: ''
    },
    users: {
        isFetching: false,
        users: []
    }
}

const formReducer = (state = initialState.form, action) => {
    switch(action.type) {
        case CHANGE_NAME:
            return {
                ...state,
                name: action.name
            }
        case CHANGE_STATUS: 
            return {
                ...state,
                status: action.status
            }
        case INITIALIZE_FORM:
            return initialState.form
        default:
            return state
    }
}

const usersReducer = (state = initialState.users, action) => {
    switch(action.type) {
        case REQUEST_DATA:
            return {
                ...state,
                isFetching: true
            }
        case RECEIVE_DATA_SUCCESS:
            return {
                ...state,
                isFetching: false,
                users: action.users
            }
        case RECEIVE_DATA_FAILED:
            return {
                ...state,
                isFetching: false
            }
        default: 
            return state;
    }
}

const rootReducer = combineReducers({
    form: formReducer,
    users: usersReducer
})

export default rootReducer;
```
入力フォーム用のformReducerとデータベースの読み取りの時用のusersReducerを作って, rootReducerでまとめています.

#action
actionは以下のような感じになります.

```:client/src/actions/index.js
export const CHANGE_NAME = 'CHANGE_NAME';
export const CHANGE_STATUS = 'CHANGE_STATUS';
export const CHANGE_USTATUS = 'CHANGE_USTATUS';
export const INITIALIZE_FORM = 'INITIALIZE_FORM';
export const REQUEST_DATA = 'REQUEST_DATA';
export const RECEIVE_DATA_SUCCESS = 'RECEIVE_DATA_SUCCESS';
export const RECEIVE_DATA_FAILED = 'RECEIVE_DATA_FAILED';

export const changeName = (name) => ({
    type: CHANGE_NAME,
    name
})

export const changeStatus = (status) => ({
    type: CHANGE_STATUS,
    status
})

export const initializeForm = () => ({
    type: INITIALIZE_FORM
})

export const requestData = () => ({
    type: REQUEST_DATA
})

export const receiveDataSuccess = (users) => ({
    type: RECEIVE_DATA_SUCCESS,
    users
})

export const receiveDataFailed = () => ({
    type: RECEIVE_DATA_FAILED
})
```

```changeUstatus``` はstatus更新のための入力フォーム用です.

#store
storeを作ります.

```client/src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import App from './components/App'
import rootReducer from './reducers'
import { Provider } from 'react-redux'

const store = createStore(rootReducer);

console.log(store);

ReactDOM.render (
  <Provider store={store}>
      <App />
  </Provider>
  ,document.getElementById('root')
)
```

#component
componentは全部で3つで```App.js```が親コンポーネント, ```Form.js```と```List.js```がその子コンポーネントという構成になっています.
なお, 今回は```mapDispatchToProps```と```mapStateToProps```を使いたかったので非常に回りくどい書き方をしております.
シンプルに書きたい方は[上で紹介した記事](https://qiita.com/muiscript/items/573247b12ff0bc4e5d3c)をご参照ください.

```client/src/compoents/App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { changeName, changeStatus, initializeForm, requestData, receiveDataSuccess, receiveDataFailed } from '../actions';
import Form from './Form';
import List from './List';

class App extends Component {
    render() {
        return (
            <div>
              <Form 
                changeName={this.props.changeName} 
                changeStatus={this.props.changeStatus} 
                initializeForm={this.props.initializeForm}
                requestData={this.props.requestData}
                receiveDataSuccess={this.props.receiveDataSuccess}
                receiveDataFailed={this.props.receiveDataFailed}
                name={this.props.name}
                status={this.props.status}
                />
              <List
                initializeForm={this.props.initializeForm}
                requestData={this.props.requestData}
                receiveDataSuccess={this.props.receiveDataSuccess}
                receiveDataFailed={this.props.receiveDataFailed}
                isFetching={this.props.isFetching}
                ustatus={this.props.ustatus}
                users={this.props.users}
                />
            </div>
        )
    }
} 

const mapDispatchToProps = ({ changeName, changeStatus, initializeForm, requestData, receiveDataSuccess, receiveDataFailed });

const mapStateToProps = state => ({ name: state.form.name, status: state.form.status,  users: state.users.users, isFetching: state.users.isFetching });

export default connect(mapStateToProps,mapDispatchToProps)(App)
```

以下はユーザーを登録するためのFormコンポーネントです.

```client/src/compoents/Form.js
import React from 'react';
import axios from 'axios';

const ROOT_ENDPOINT = 'http://localhost:3001';

const Form = ({ name, status, changeName, changeStatus, initializeForm, requestData, receiveDataSuccess, receiveDataFailed }) => {
   const createUser = e => {
       if(name.length > 10 || status.length > 10) {
           alert('文字数が多いです');
       } else {
           e.preventDefault();
           axios({
             method: 'post',
             url: ROOT_ENDPOINT + '/user/create',
             data: {
                name: name,
                status: status
             }
           })
           .then(res => {
             initializeForm();
             const _users = res.data;
             console.log(_users);
             receiveDataSuccess(_users);
           })
           .catch(err => {
             console.log(err);
             alert('登録に失敗しました')
             receiveDataFailed();
           })
       }
    }

    return (
        <div>
            <form onSubmit={e => createUser(e)}>
                <label>
                    name:
                    <input value={name} onChange={e => changeName(e.target.value)} />
                </label>
                <label>
                    status:
                    <input value={status} onChange={e => changeStatus(e.target.value)} />
                </label>
                <button type="submit">register</button>
            </form>
        </div>
    )
}

export default Form;
```

以下はユーザーを表示, 更新, 削除するためのListコンポーネントです.

```client/src/compoents/List.js
import React from 'react';
import axios from 'axios';

const ROOT_ENDPOINT = 'http://localhost:3001';


class List extends React.Component {
    constructor(props) {
        super(props);
    }

    componentDidMount = () => {
        this.fetchData();
    }

    handleChange = name => event => {
        this.setState({ [name]: event.target.value });
    };

    fetchData = () => {
        this.props.requestData();
        axios.get(ROOT_ENDPOINT + '/user')
        .then(res => {
            const _users = res.data;
            this.props.receiveDataSuccess(_users);
        })
        .catch(err => {
            console.log(err);
            this.props.receiveDataFailed();
        })
    }

    updateUser = (id) => {
        if(this.state[`${id}`].length > 10) {
            alert('文字数が多いです');
        } else {
            this.props.requestData();
            axios({
              method: 'put',
              url: ROOT_ENDPOINT + '/user/update',
              data: {
                id: id,
                status: this.state[`${id}`]
              }
            })
            .then(res => {
              const _users = res.data;
              this.props.receiveDataSuccess(_users);
            })
            .catch(err => {
              console.log(err);
              alert('更新に失敗しました');
              this.props.receiveDataFailed();
            })
        }
    }

    deleteUser = (id) => {
        this.props.requestData();
        axios({
            method: 'delete',
            url: ROOT_ENDPOINT + '/user/delete',
            data: {
                id: id
            }
        })
        .then(res => {
            const _users = res.data;
            this.props.receiveDataSuccess(_users);
        })
        .catch(err => {
            console.log(err);
            alert('削除に失敗しました');
            this.props.receiveDataFailed();
        })
    }

    render() {
        return (
            <div>
                {
                    this.props.isFetching
                        ? <h2>Now Loading...</h2>
                        : <div>
                            <ul>
                                {this.props.users.map(user => (
                                    <li key={user.id}>
                                        {`${user.name}: ${user.status}`}
                                        <input  onChange={this.handleChange(`${user.id}`)} />
                                        <button onClick={() => this.updateUser(user.id)}>update</button>
                                        <button onClick={() => this.deleteUser(user.id)}>delete</button>
                                    </li>
                                ))}
                            </ul>
                          </div>
                }
            </div>
        )
    }
}

export default List;
```

ソースコードはこちらです
https://github.com/melonattacker/crud-app

#デモ
![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326169/91065885-b4a1-800d-55cd-f99a8e28db1a.gif)

#Happy Hacking :sunglasses: !