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

#概要
前回の記事までは、Todoを追加する『Add Todo』と、Todoの未・済を切り替える『Toggle Todo』の機能を実装して参りました。今回は、選択したフィルタによって表示するTodoを変更する__『Filter Todo』__の機能を実装していきたいと思います！

『Add Todo』に関しては[こちら](https://qiita.com/micropig3402/items/8e44bc042eda13b98c95)
『Toggle Todo』に関しては[こちら](https://qiita.com/micropig3402/items/2413d6767baf619b8df5)

#完成品
『All』,『Active』,『Completed』のリンクを押すことで、ページに表示されるTodoが変わります！
![ezgif.com-optimize (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/1ec1aaa3-45bb-2448-c0c4-cf92967a116f.gif)

#Action Creatorの作成
新たに`setVisibilityFilter`を追加します。ここでは、フィルター（SHOW_COMPLETEDなど）を受け取り、typeとfilterを返します。

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

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE',
};

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

export const toggleTodo = id => {
  return {
    type: TOGGLE_TODO,
    id,
    //index: index
  };
};

export const setVisibilityFilter = filter => {
  return {
    type: SET_VISIBILITY_FILTER,
    filter,
    // filter: filter
  };
};

```

#reducerの作成
初期stateを`SHOW_ALL`とし、`action.type`が`SET_VISIBILITY_FILTER`の際に`action.filter`を新しいstateとして返します。

```react:src/reducers/visibilityFilter.js
import {VisibilityFilters} from '../actions';

const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter;
    default:
      return state;
  }
};

export default visibilityFilter;

```

新しくreducerを作ったので、`src/reducers/index.js`にてcombineReducers関数に加えましょう！

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

const todoApp = combineReducers({todos, visibilityFilter});

export default todoApp;
```

#動作確認
`src/index.js`にて、正しくデータが格納されるか手動で確認してみましょう！

```react:src/index.js
import {addTodo, toggleTodo, setVisibilityFilter} from './actions';

console.log(store.getState()); /// "SHOW_ALL"
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));
console.log(store.getState()); /// "SHOW_COMPLETED"
```
<img width="661" alt="スクリーンショット 2019-05-06 20.35.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/b20d650a-be4c-d839-0b85-820793106646.png">

ここまでで、Action Creatorとreducerを作成し、フィルターの値をstoreに格納することができました。

次からは、フィルターの値によってviewの表示を変更できるようにしましょう！

#VisibleTodoListを修正する

todos.filter()のように配列のメソッドのフィルタを用いることで、todoのcompleted属性によって、新たに作成した配列を返します。

- [配列のfilterメソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)

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

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case VisibilityFilters.SHOW_ALL:
      return todos;
    case VisibilityFilters.SHOW_COMPLETED:
      return todos.filter(todo => todo.completed);  //todos.filter()は配列のメソッドのフィルタ 
    case VisibilityFilters.SHOW_ACTIVE:
      return todos.filter(todo => !todo.completed); //todos.filter()は配列のメソッドのフィルタ
  }
};

const mapStateToPorops = state => {
  return {todos: getVisibleTodos(state.todos, state.visibilityFilter)};
};

const mapDispatchToProps = dispatch => {
  return {
    toggleTodo: id => {
      dispatch(toggleTodo(id));
    },
  };
};

const VisibleTodoList = connect(
  mapStateToPorops,
  mapDispatchToProps
)(TodoList);

export default VisibleTodoList;

```

先ほどと同様に`src/index.js`にて手動で動作確認をしましょう！

```react:src/index.js
import {addTodo, toggleTodo, setVisibilityFilter} from './actions';

store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));
```

#Linkを作成する
表示したいTodoの種類をLinkをクリックすることによって表示できるようにします。

まずは、とりあえずLinkを表示させましょう！

props.childrenはコンポーネントの中身を取得できます。Linkコンポーネントを使うときの、`<Link>xxx</Link>`の`xxx`です。

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

const Link = ({children, onClick}) => {
  return (
    <a href="#">{children}</a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Link;

```
LinkコンポーネントはFooterコンポーネントで使用します。

```react:src/components/Footer.js
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = () => {
  return (
    <p>
      Show: <Link>All</Link>
      {', '}
      <Link>Active</Link>
      {', '}
      <Link>Completed</Link>
    </p>
  );
};

export default Footer;

```
FooterコンポーネントはAppコンポーネントで表示します！

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

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

export default App;

```
これでリンクの表示が完了です。

#リンクをクリックしたときにフィルターの値を変える
リンクをクリックした際にフィルターの値（SHOW_ALLなど）を変えるには、リンクをクリックした際に、`dispatch(setVisibilityFilter())`を呼び出せるようにします。
connect関数を使って、propsにdispatchを渡せるようにしましょう！

```react:src/containers/FilterLink.js
import {connect} from 'react-redux';
import {setVisibilityFilter} from '../actions';
import Link from '../components/Link';

const mapStateToProps = (state, ownProps) => {
  return {state: state};
};

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter));
    },
  };
};

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link);

export default FilterLink;

```

`src/components/Footer.js`でLinkとmapStateToProps,mapDispatchToProps
をconnectさせた『FilterLink』を使います。
この際に、mapDispatchToPropsのonClick関数にフィルターの値を渡すため`filter="SHOW_ALL"`のように記述します。

```react:src/components/Footer.js
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = () => {
  return (
    <p>
      Show: <FilterLink filter="SHOW_ALL">All</FilterLink>
      {', '}
      <FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
      {', '}
      <FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>
    </p>
  );
};

export default Footer;
```

#Linkをクリックした際にonClick関数を呼ぶ
『preventDefault』は『デフォルトの動作を発生させない』という意味です。今回はaタグの中で使っているのですが、これは『aタグのherfで指定されたURLへ遷移する動作を発生させない』という意味を持っております。

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

const Link = ({children, onClick}) => {
  return (
    <a
      href="#"
      onClick={e => {
        e.preventDefault();
        onClick();
      }}>
      {children}
    </a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired,
};

export default Link;

```
これで、Linkを押すとviewの表示が切り替わる動作を実装することができました。
(Linkクリック→`onClick関数`が呼び出される→`dispatch(setVisibilityFilter())`が呼び出される→storeに保存されているフィルターの値が更新→viewが書き換わる)

#現在activeなリンクを押せなくする
現在activeなリンクを押せなくするために、activeなリンクをただのテキストに変更する機能を実装いたします。

ここでは,Linkコンポーネントの現在の状態を知るために`props.active`としてデータを渡します。

```react:src/containers/FilterLink.js
const mapStateToProps = (state, ownProps) => {
  return {active: ownProps.filter === state.visibilityFilter};
};
```

そして、`active`の状態によってテキストを返すか、リンクを返すかを`src/components/Link.js`にて判断いたします。

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

const Link = ({active, children, onClick}) => {
  if (active) {
    return <span>{children}</span>;
  }

  return (
    <a
      href="#"
      onClick={e => {
        e.preventDefault();
        onClick();
      }}>
      {children}
    </a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired,
};

export default Link;
```

以上で、『Filter Todo』の実装は完了です！

#公式のBasicTutorial完了
これで公式のBasicTutorialと同じ機能が実装できたように思います。

![ezgif.com-optimize (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/bf66ca2b-47a2-7a86-48a5-e84e2e224c21.gif)


#リファレンス
- [Redux Basic Tutrial](https://redux.js.org/basics/basic-tutorial)
- [Redux ExampleのTodo Listをはじめからていねいに(3)](https://qiita.com/xkumiyu/items/1ba476b8043b71561f52)
- [配列のfilterメソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
- [JavaScriptのpreventDefault()って難しくない？preventDefault()を使うための前提知識](https://qiita.com/tochiji/items/4e9e64cabc0a1cd7a1ae)