React + ReduxでTodoアプリを作ってみよう！『Add Todo編』

#概要
前回までの記事では、ReactとReduxの基本をまとめさせて頂きました。今回からは、さらにもう一歩踏み込んでReactとReduxを用いたTodoアプリを作ってみたいと思います。（参考：[Redux Basic Tutrial](https://redux.js.org/basics/basic-tutorial)）

前回の記事
[『Redux』を用いて状態管理をしてみよう！](https://qiita.com/micropig3402/items/ca6f14f92d6238a0aade)

今回は、TodoをTodoListに追加する__『Add Todo』__の機能を実装したいと思います。

#Action & Action Creator
まずは、ActionとAction Creatorの定義から始めていきましょう！
Actionのtypeは`ADD_TODO`と定義し、`text`は入力されたTodo、`id`は追加されていくTodoの番号となります。

```react:src/actions/index.js
export const ADD_TODO = 'ADD_TODO';

let nextTodoId = 0;
export const addTodo = text => {
 return {
  type: ADD_TODO,
  id: nextTodoId++,
  text, //text: textを省略
 };
};
```

#Reducer
次にReducerを定義していきましょう！
まずは全てのReducerを結合するファイルである`src/reducers/index.js`を作成しましょう！`combineReducers()`などに関して知りたい方は[前回の記事](https://qiita.com/micropig3402/items/ca6f14f92d6238a0aade)をご覧ください

```react:src/reducers/index.js
import {combineReducers} from 'redux';
import todos from './todo';

const todoApp = combineReducers({todos});

export default todoApp;
```

続いて、実際にActionから発行されるtypeを受け取って状態遷移を行う`src/reducers/todo.js`を書いていきましょう！
`...state`によって今まで追加されてきたTodoが、`{id: action.id, text: action.text}`によって新しく追加されたTodoが配列に格納され返ってきます。

```react:src/reducers/todo.js
import {ADD_TODO} from '../actions';

const todos = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, {id: action.id, text: action.text}];
    default:
      return state;
  }
};

export default todos;
```

#store
storeはアプリケーション内のstate(状態)を管理しているところです。詳しくは先ほどと同様に[前回の記事](https://qiita.com/micropig3402/items/ca6f14f92d6238a0aade)を参照してください。

```react:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {createStore} from 'redux';

import App from './components/App';
import {addTodo} from './actions';
import * as serviceWorker from './serviceWorker';
import reducers from './reducers';

let store = createStore(reducers);

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

#Presentational Components

『Presentational Components』はpropsとして受け取ったデータを表示することに専念するコンポーネントになります。

`src/components/Todo.js`ではpropsとして受け取ったデータを表示します。

```react:src/components/Todo.js
import React from 'react';
import PropTypes from 'prop-types';

const Todo = ({text}) => {
  return <li>{text}</li>;
};

Todo.propTypes = {
  text: PropTypes.string.isRequired,
};

export default Todo;
```

一方`src/components/TodoList.js`では、`map`を用いてpropsとして受け取った`todos`をTodo Componentに渡しています。`{...todo}`はtodoの全ての要素を表しており、`id={todo.id}`、`text=todo.text`と同じ意味を持っています。

```react:src/components/TodoList.js
import React from 'react';
import propTypes from 'prop-types';
import Todo from './Todo';

const TodoList = ({todos}) => {
  return (
    <ul>
      {todos.map(todo => (
        <Todo key={todo.id} {...todo} />
      ))}
    </ul>
  );

  TodoList.propTypes = {
    todos: propTypes.arrayOF(
      propTypes.shape({
        id: propTypes.number.isRequired,
        text: propTypes.string.isRequired,
      }).isRequired
    ).isRequired,
  };
};

export default TodoList;
```

#Container Components
Container componentはPresentational componentに具体的なデータやコールバック関数を与えるコンポーネントです。

`src/containers/VisibleTodoList.js`では、まずは`mapStateToPorops`を用いてstateから必要な情報をpropsとしてマッピングさせます。そしてconnect関数を用いてTodoList Componentとstateを結びつけて、`VisibleTodoList`としてexportします。

```react:src/containers/VisibleTodoList.js
import {connect} from 'react-redux';
import TodoList from '../components/TodoList';

const mapStateToPorops = state => {
  return {todos: state.todos};
};

const VisibleTodoList = connect(mapStateToPorops)(TodoList);

export default VisibleTodoList;
```

これで、ブラウザにTodoを表示させることが出来るようになりましたが、まだTodoを入力するFormがないので作成していきます。

```react:src/containers/AddTodo.js
import React from 'react';
import {connect} from 'react-redux';
import {addTodo} from '../actions';

let AddTodo = ({dispatch}) => {
  let input;

  return (
    <div>
      <input
        ref={node => {
          input = node;
        }}
      />
      <button
        onClick={() => {
          dispatch(addTodo(input.value));
          input.value = '';
        }}>
        Add Todo
      </button>
    </div>
  );
};

AddTodo = connect()(AddTodo);

export default AddTodo;
```
`src/containers/AddTodo.js`では`dispatch(addTodo(input.value));`と書くことで、buttonを押した際にinput要素に入力された内容をTodoに追加出来るようになっています。

またinput要素内でrefを用いておりますが、refを用いることで、あらかじめ宣言していた`input`に入力された内容を格納することができます。今回は以下に示す３つの方法の中でrefコールバック属性を用いる方法を選択しています。

>Refsを扱うには３つ方法があります。

>- React.createRefメソッドを用いる（v16.3.0以上で利用可能）
- refコールバック属性を用いる
- ref文字列属性を用いる（非推奨 v17で削除）

参考：[ReactのRefsを正しく取り扱う](https://qiita.com/tatakahashiap/items/3ecf3ed3d026c2b9ee8d)

#ブラウザに表示させてみよう！
では、`src/components/App.js`に`<AddTodo />`と`<VisibleTodoList />`を追加してブラウザに表示させてみましょう！

```react:src/components/App.js
import React from 'react';
import VisibleTodoList from '../containers/VisibleTodoList';
import AddTodo from '../containers/AddTodo';

const App = () => {
  return (
    <div className="App">
      <AddTodo />
      <VisibleTodoList />
    </div>
  );
};

export default App;
```

これで『Add Todo』の機能は実装できたと思います。次回は、Todoの完了・未完了を切り替える『Toggle Todo』の機能を実装していきましょう！

![ezgif.com-optimize (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/b1b7eb0d-8d92-f101-02ba-821dd61e6441.gif)


#リファレンス
- [Redux Basic Tutrial](https://redux.js.org/basics/basic-tutorial)
- [Redux ExampleのTodo Listをはじめからていねいに(1)](https://qiita.com/xkumiyu/items/9dfe51d2bcb3bdb06da3)
- [分割代入
](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
- [[redux] Presentational / Container componentの分離 - react-redux.connect()のつかいかた](https://qiita.com/tuttieee/items/a3ca7d9d415049d02d60)
- [関数
](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Functions)
- [《JavaScript》ES6の分割代入のはじめかた。](https://qiita.com/uto-usui/items/a9d17447fe81c17c41fa)
- [スプレッド構文
](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
- [ReactのRefsを正しく取り扱う](https://qiita.com/tatakahashiap/items/3ecf3ed3d026c2b9ee8d)