React+Redux+Material-UI(+Redux Form)でReactTutorialの先へ行く[前編]

Todoアプリを作っている記事はよくみるものの、それの一つ上の段階の記事がなかなかないと思ったので、書くことにしました。
React, Reduxを使ってTodoAppのtutorialを作った方を対象としています。

後編書きました -> [React+Redux+Material-UI(+Redux Form)でReactTutorialの先へ行く[後編]](https://qiita.com/yasudadesu/items/488becb22276e334559f)

## 目的
[redux-form](https://redux-form.com/7.4.2/)と[material-ui](https://material-ui.com/)を用いて、ある程度綺麗な見た目で、**複数項目を入力できるTodoApp**を作成する

この前半の記事では、redux-formを使ってロジックを実装するまでについて書いています。具体的には↓を作ります。
![todo-delete.gif](https://qiita-image-store.s3.amazonaws.com/0/190157/b0abc1f3-1a0c-d68d-e957-70aa7882faa1.gif)

また、完成したコードは[こちら](https://github.com/yasudadesu/todo-next)においてあります。

[2018/8/27]
編集リクエストをいただきました
discription -> description に変更しました。
khskさんありがとうございます。


## 準備

### 雛形の作成と必要パッケージのインストール

```shell
% create-react-app todo-next
% cd todo-next
% npm install -S redux-form  @material-ui/core classnames redux react-redux
% npm install -D redux-devtools
```

### ディレクトリ構成

`src`ディレクトリ以下のみを触ります。
`src`ディレクトリの構成は以下です。
具体的には、`actions`, `components`, `containers`, `reducers`ディレクトリを作成し、その下にファイルを配置し、必要のないファイルを削除しています。

```shell:
% cd src && tree
.
├── actions
│   └── todos.js
├── components
│   └── todos.js
├── consts.js
├── containers
│   ├── todoForm.js
│   └── todos.js
├── index.js
├── logo.svg
├── reducers
│   └── todos.js
└── store.js
```

## 実装
まずは上に示したGIFの、追加機能のみがある（削除機能のない）ものを作っていきます。

### `src/index.js`
`registerServiceWorker`は今回は必要ないので消しています。
`react-redux`のProviderを用いてstoreに反映させます。

```javascript:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import store from './store';
import { Provider } from 'react-redux';
import TodoApp from './containers/todos';

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

```

### `src/consts.js`
定数を設定していきます。今回はactionの名前を格納しました。
簡単な場合、例えば今回のように`ADD_TODO`が１つや２つ程度ならいいのですが、増えてきた場合や名前を変更したくなった際に、まとめておかないと直す箇所が多くなってしまうという問題が出てきます

```javascript:src/consts.js
export const actionNameList = {
  addTodo: "ADD_TODO",
}
```

### `src/store.js`
`addTodo`をするためのReducerと、redux-formがよしなにしてくれるReducerの２つが存在しているので`combineReducers`を使用しています。
また、`process.env.~~`の部分はReduxのdevツールを使用するために書いています。
これを使うことで、**storeに対する動作や状態がブラウザから確認可能**となります。

```javascript:src/store.js
import { createStore, combineReducers } from 'redux';
import { reducer as reduxFormReducer } from 'redux-form';
import todoListReducer from './reducers/todos';

const reducer = combineReducers({
  form: reduxFormReducer,
  todoList: todoListReducer,
});

const store = createStore(
    reducer,
    process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default store;
```

### `src/actions/todos.js`
定数を呼び出し、typeに格納しています。

```javascript:src/actions/todos.js
import { actionNameList } from '../consts';

export const addTodo = (todo) => ({
  type: actionNameList.addTodo,
  payload: {
    todo
  }
})
```

### `src/components/todos.js`
idの管理などは一旦特にせず、reduxに依存しない部分のみを書きました。
引数の`todoList`を受け取り、それを元にただ描画するためのコンポネントです。

```javascript:src/components/todos.js
import React from 'react';

export default class TodoList extends React.Component {
  render() {
    const { todoList } = this.props;
    return (
      <table>
      <thead>
        <tr>
          <th>Title</th>
          <th>Description</th>
        </tr>
      </thead>
      <tbody>
        {
          todoList.map((todo, i) => (
          <tr key={i}>
            <td>{todo.title}</td>
            <td>{todo.description}</td>
          </tr>
          )
        )}
      </tbody>
      </table>
    );
  }
}
```

### `src/containers/todoForm.js`
[redux-form sample](https://redux-form.com/7.4.2/examples/simple/)を参考に書き進めました。
`Title`と`Description`を受け取る設定になっています。
redux-formはSubmit時のAction, Reducerの動作を代わりにやってくれるのでとても便利ですね。
（留意：redux-formのReducerの宣言はstore.jsでしました）

```javascript
import React from 'react';
import { Field, reduxForm } from 'redux-form';

const TodoForm = props => {
  const { handleSubmit, pristine, reset, submitting } = props;
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Todo Title</label>
        <div>
          <Field
            name="title"
            component="input"
            type="text"
            placeholder="題名"
          />
        </div>
      </div>
      <div>
        <label>Todo Description</label>
        <div>
          <Field
            name="description"
            component="input"
            type="text"
            placeholder="説明"
          />
        </div>
      </div>
      <div>
        <button type="submit" disabled={pristine || submitting}>送信</button>
        <button type="button" disabled={pristine || submitting} onClick={reset}>
          クリア
        </button>
      </div>
    </form>
  );
};

export default reduxForm({
  form: 'todo',
})(TodoForm);
```

### `src/containers/todos.js`
reduxの流儀にのっとり、storeから`todoList`を引数にうけとり、それを元にした`TodoList`コンポネントと`redux-form`を使った`TodoForm`コンポネントを描画しています。

```javascript:src/containers/todos.js
import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../actions/todos';
import TodoForm from './todoForm';
import TodoList from '../components/todos';

class TodoApp extends React.Component {
  render() {
    return (
      <div>
        <h2>TodoList</h2>
        <TodoForm onSubmit={this.props.addTodo} 
        />
        
        <hr />
        <TodoList todoList={this.props.todoList} />
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  todoList: state.todoList,
});
const mapDispatchToProps = (dispatch) => ({
  addTodo: (todo) => dispatch(addTodo(todo)),
  deleteTodo: (index) => dispatch(deleteTodo(index)),
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
```

### `src/reducers/todos.js`
よくみた形だと思います、addTodoされた時に、既存のTodoListと新しく追加されたTodoを返り値として返します。

```javascript:src/reducers/todos.js
import { actionNameList } from '../consts';
const initialState = [];

export default function todoListReducer(state=initialState, action) {
    switch (action.type) {
        case actionNameList.addTodo:
            return [...state, action.payload.todo];
        default:
            return state;
    }
}
```

## 動作確認

無事、titleとdescriptionの２つを同時にSubmitすることができました。
![todoapp.gif](https://qiita-image-store.s3.amazonaws.com/0/190157/8adbfce1-7528-efed-eb4f-1959643cde1b.gif)

## 削除の動作の実装
追加だけではなく、次は削除機能を追加してみます。
具体的には、『削除ボタンを押したタイミングでactionが発行され、`todoList`配列の中から削除ボタンを押されたものを削除し、描画する』という処理になります。
また、以下では`...`で省略を表しておりますので、あらかじめご了承ください。

### `consts.js`
DELETE用のtypeの名前を定数として新しく格納します。

```javascript
export const actionNameList = {
  addTodo: "ADD_TODO",
  deleteTodo: "DELETE_TODO",
}
```

### `reducers/todos.js`
deleteのcaseを設定します。前までに書いてる箇所の省略には気をつけてください

```javascript
...
    case actionNameList.deleteTodo:
      state.splice(action.payload.id, 1);
      return [...state];
...
```

### `actions/todos.js`
`deleteTodo`というアクションを追加します。`addTodo`とは違って引数にとっているのがtodoのデータではなくindexであることに注意してください。


```javascript
...
export const deleteTodo = (index) => ({
  type: actionNameList.deleteTodo,
  payload: {
    id: index,
  }
})
```

### `components/todos.js`
テーブル要素に操作カラムと削除ボタンを追加していきます。これをクリックした際に、引数としてうけとっている`handleDelete`関数が呼ばれるという形式になっています。

```javascript
...
        <tr>
          <th>Title</th>
          <th>Description</th>
          <th>操作</th>
        </tr>
...
          <tr key={i}>
            <td>{todo.title}</td>
            <td>{todo.description}</td>
            <td>
              <input type="button" value="削除" onClick={() => this.props.handleDelete(i)}/>
            </td>
          </tr>
...
```

### `containers/todos.js`
`deleteTodo`を新たにimportして、`todoList`での削除を可能にします。
また、`handleDelete`関数を`bind`していますが、これがなければ`TypeError: Cannot read property 'deleteTodo' of undefined`が起こってしまいますので注意してください。

```javascript
...
import { addTodo, deleteTodo } from '../actions/todos';
...
class TodoApp extends React.Component {
  constructor(props) {
    super(props)
    this.handleDelete = this.handleDelete.bind(this)
  }
  
  handleDelete(index) {
    this.props.deleteTodo(index)
  }

  render() {
    return (
      <div>
        <h2>TodoList</h2>
        <TodoForm onSubmit={this.props.addTodo} 
        />
        
        <hr />
        <TodoList 
          todoList={this.props.todoList} 
          handleDelete={this.handleDelete}
        />
      </div>
    )
  }
}
...
```


## おわりに
TODOアプリだけではさらに何ができるのかがわかりにくく、また難しいものを見ると何が起こっているのかがわからないということがありそうだったので、React-Reduxを始める２歩目として読んでいただけるようなレベルを目指しましたが、いかがでしたでしょうか。
少し長くなってしまったので、material-uiの方は別記事にしようと思います。
読んでいただきありがとうございました。
また、もっと良い方法がある等ございましたらご教授いただけると幸いです。