Redux ExampleのTodo Listをはじめからていねいに(3)

[Reduxの公式ExampleにあるTodo List](http://redux.js.org/docs/basics/ExampleTodoList.html)を機能ごとに作っていくシリーズ最終回です。

1回目では、TodoをTodo Listに追加する「Add Todo」を作りました。
2回目では、Todoの完了・未完了を切り替える「Toggle Todo」の機能を作りました。
3回目の今回は、表示するTodo Listを完了または未完了のTodoだけにする「Filter Todo」機能を作りたいと思います。

1,2回目を読んでない人は、そちらを先にどうぞ。

* [Redux ExampleのTodo Listをはじめからていねいに(1)](http://qiita.com/xkumiyu/items/9dfe51d2bcb3bdb06da3)
* [Redux ExampleのTodo Listをはじめからていねいに(2)](http://qiita.com/xkumiyu/items/e7e1e8ed6a5d6a6e20dd)

機能開発の際、どこから手をつけるかは悩ましいところですが、私はactionから作ることが多いです。
だいたいactionCreatorとreducerの関数を作って、手動で（index.jsから）動いているの確認してから、
stateもしくはdispatchをpropとしてラップするcomponents(containers)を作るって感じにしています。

また、「Filter Todo」では、以下の3つのフィルターによって表示を変更する機能です。

* SHOW_ALL: 全部表示
* SHOW_COMPLETED: 完了しているtodoのみ
* SHOW_ACTIVE: 完了していないtodoのみ

なので、

1. actionCreatorとreducerでフィルターの値をstore(state)に格納
2. フィルターの値によってviewを変更（手動でフィルターを操作して動作確認）
3. リンクをクリックしてフィルターを操作してviewを変更

の順番に開発していきます。

# 1. actionCreatorとreducerでフィルターの値をstore(state)に格納

## actionCreatorの作成

actionCreatorはフィルター（SHOW_ALLとか）を受け取ってそれを返すだけです。

``` actions/index.js
export const setVisibilityFilter = (filter) => {
  return {
    type: 'SET_VISIBILITY_FILTER',
    filter
  }
}
```

## reducersの作成

reducersも単純で受け取ったactionのfilterを新しいstateとして
返します。なので、stateとしてfilterの値が格納されます。

todosとは別の変数として格納するので、新しくreducersを作っています。
初期値にはSHOW_ALLを与えています。

``` reducers/visibilityFilter.js
const visibilityFilter = (state = 'SHOW_ALL', action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default visibilityFilter
```

ファイルも分けていますので、reducers/index.jsに登録が必要です。
最終的なstoreの値は`{todos: [todo1, todo2], visibilityFilter: 'SHOW_ALL'}`みたいな感じになります。

``` reducers/index.js
import visibilityFilter from './visibilityFilter'
const todoApp = combineReducers({
  todos,
  visibilityFilter
})
```

## 手動で動作確認

一応、正しく格納できるか確認します。

``` index.js
import { setVisibilityFilter } from './actions'

console.log(store.getState()) // => Object {todos: Array[0], visibilityFilter: "SHOW_ALL"}
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'))
console.log(store.getState()) // => Object {todos: Array[0], visibilityFilter: "SHOW_COMPLETED"}
```

storeの初期値としてSHOW_ALLが格納されており、setVisibilityFilterによって、
値が変更されていることが分かります。


# 2. フィルターの値によってviewを変更（手動でフィルターを操作して動作確認）

actionとreducerが完成し、storeにフィルターの値を格納できることができたので、
その値でviewを変更してみます。

## VisibleTodoListコンテナの修正

todoリストを表示しているのはTodoListコンポーネントですが、表示するtodoリストを
propsとして渡しているのはVisibleTodoListコンテナになります。

これまでは以下のコードで、格納されたtodosをそのまま渡していました。

``` js
const mapStateToProps = (state) => {
  return { todos: state.todos }
}
```

これをフィルターの値によって変更するよう修正します。
getVisibleTodos関数によって、フィルターに合致するtodosを返しています。

同じ名前でややこしいですが、todos.filterのfilterは文字列ではなく
[配列のメソッドのfilter](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)です。

``` containers/VisibleTodoList.js
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter((t) => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter((t) => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
```

## 手動で動作確認

先ほどと同じように動作確認をします。
SHOW_COMPLETEDを指定すると、完了しているもののみが表示されているはずです。

``` index.js
import { setVisibilityFilter } from './actions'
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'))
```

# 3. リンクをクリックしてフィルターを操作してviewを変更

フィルターの値を格納することと、その値によって表示を変更することができたので、
手動ではなくリンクをクリックしてフィルターの値を変え、viewに反映するとこを作っていきます。

## とりあえず、リンクを表示させる

まず、リンクを作ります。押しても何もできませんが。。
Linkコンポーネントは単なるリンクです。

[props.children](http://qiita.com/koba04/items/43200b6fd6e6f43f0d8d#propschildren)はコンポーネントの中身を取得できます。
Linkコンポーネントを使うときの、`<Link>xxx</Link>`のxxxです。

``` components/Link.js
import React, { PropTypes } from 'react'

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

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

export default Link
```

LinkコンポーネントはFooterコンポーネントで表示し、
FooterコンポーネントはAppコンポーネントに表示します。

``` components/Footer.js
import React from 'react'
import Link from './Link'

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

export default Footer
```

``` components/App.js
import Footer from './Footer'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)
```

これで、とりあえずリンクが表示されました。

本当に単なるリンク（外部リンクとか）を作りたいだけならLinkコンポーネントなんか
つくらず、`<a href="#">All</a>`とかをFooterコンポーネントに書けばできます。

これからLinkコンポーネントでいろいろつくっていくので分けています。

## Linkコンテナで`dispatch(setVisibilityFilter())`を呼び出せるようにする

それでは、リンクをクリックしたときのフィルターの値を変える機能を作ります。

これは、クリックしたときに`dispatch(setVisibilityFilter())`を呼び出すことができればよく、
これはToggle Todoと同じく、connectをつかってpropsにdispatchを渡すことで実現できます。

connectの引数は、`connect([mapStateToProps], [mapDispatchToProps])`であるため、
mapDispatchToPropsだけを使うときでも、mapStateToPropsが必要になります。
またmapStateToPropsは、objectを返す関数でないとだめなので、とりあえずstateをそのまま返します。
（もっといい方法があるかもしれませんが、とりあえずは問題なく動きます。）

また、mapDispatchToProps、mapStateToPropsの引数はそれぞれ

* mapDispatchToProps(dispatch, [ownProps])
* mapStateToProps(state, [ownProps])

となっており、propsとして渡すだけでなく、自身のpropsを使うこともできます。
このあたりは[react-reduxのapiのドキュメント](https://github.com/reactjs/react-redux/blob/master/docs/api.md)に詳しく書いてあります。

FilterLinkコンテナのmapDispatchToPropsでは、propsとしてonClick関数を渡しており、
この関数は、自身のpropsのフィルターの値をsetVisibilityFilter関数に渡しています。

これでLinkコンポーネントでonClick関数を呼び出せばOKです。

``` 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
```

先程は、FooterコンポーネントをLinkコンポーネントを使って書いていましたが、
FilterLinkコンテナとしてconnectしたので、こちらを使います。

また、onClick関数にフィルターの値を渡すため、それぞれprops.filterに
値を入れておきます。

``` components/Footer.js
import FilterLink from '../containers/FilterLink'

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

これでLinkコンテナでonClick関数が使えるようになり、`dispatch(setVisibilityFilter())`を呼び出すことが
できるようになりました。

## クリックしたときにonClickを呼ぶ

LinkコンテナでonClick関数が使えるようになったので、クリックしたときにonClick関数を呼んでみます。
単なるリンクを表示していたLinkコンテナを修正します。

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

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

これでリンクを押すと`onClick()`が呼び出され、その中で`dispatch(setVisibilityFilter())`が呼び出され、storeに格納されているフィルターの値が更新され、その値にしたがってviewが書き換わるという一連の流れが実装できました。

## activeな状態なリンクを押せないようにする

最後にactiveな状態のリンクを押せないようにする。

これは現在の状態がSHOW_ALLだったらALLとうリンクを押せないようにします。
押せないようにするというか、リンクではなくテキストを表示するように変更します。

Linkコンポーネントが現在の状態を知るために、props.activeとして値を渡します。
現在のフィルターの値（state.visibilityFilter）をそのまま渡してもできますが、
自身のpropsの値と比較してture/falseで渡しています。

現在の状態がSHOW_ALLの場合、<FilterLink filter="SHOW_ALL">では、
ownProps.filterもSHOW_ALLとなるので、trueとなりそれ以外はfalseとなります。

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

Linkコンポーネントではactiveの値を受け取り、trueの場合テキストを
falseの場合リンクを返すようにしています。

``` components/Link.js
import React, { PropTypes } from 'react'

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

  return (
    <a href="#"
    // ...
    </a>
  )
}

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}
```

これでactiveな状態のリンクを押せないようになり、「Filter Todo」機能が完成しました。

1. TodoをTodo Listに追加する「Add Todo」
2. Todoの完了・未完了を切り替える「Toggle Todo」
3. 表示するTodo Listを完了または未完了のTodoだけにする「Filter Todo」
の3つの機能が完成し、[公式のExample](http://redux.js.org/docs/basics/ExampleTodoList.html)と同じ形になりました。

![redux-todos.gif](https://qiita-image-store.s3.amazonaws.com/0/35980/86ea2c70-56de-3e03-aea0-4cff4e490fb2.gif)

ここまでのソースコードは[GitHub](https://github.com/xkumiyu/redux-todos)にあげています。
（といっても[公式のExample](http://redux.js.org/docs/basics/ExampleTodoList.html)と同じですが）
また、[Github Pages](http://xkumiyu.github.io/redux-todos/)にもあげています。

Redux ExampleのTodo Listは以上になります。
もうちょっと理解してきたら[Reddit API](http://redux.js.org/docs/advanced/ExampleRedditAPI.html)も書きたいと思います。