ReduxのサンプルのTodoリストを真似てみる。Part.4

[ReduxのサンプルのTodoリストを真似てみる。Part.3](https://qiita.com/tkarasuma/items/0b2b8188fbb962868b87)の続きです。

#0.留意点
- [Redux ExampleのTodo Listをはじめからていねいに(3)](https://qiita.com/xkumiyu/items/1ba476b8043b71561f52)を参考にさせて頂いて、Reduxのコードを書いた。
- 初心者の覚書です。
- 自分の環境で動くように参考にしたコードを適当に修正している。
- Windows10 64bit , PowerShellなどで動かしている。
- 見栄えを若干よくする為にbootstrap4を利用している。

#1.stateにフィルター機能をつける

Todoリストの表示を、進行中、完了、全ての3通りのフィルターに対応させる準備。
SHOW_ACTIVE , SHOW_COMPLETED, SHOW_ALLの３つの定数をフィルターとして想定。

##actionCreator追加

```javascript:actions\index.js
let nextTodoId=0
export const addTodo= (text)=>{
    return{
        type:'ADD_TODO',
        id:nextTodoId++,
        text

    }
}

export const toggleTodo=(id)=>{
    return{
        type:'TOGGLE_TODO',
        id
    }
}

export const setVisibilityFilter =(filter)=>{
    return {
        type:'SET_VISIBILITY_FILTER',
        filter
    }
}

```

##フィルター用 reducers追加

フィルターの定数を返すだけの、visibilityFilter関数　

```javascript: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;
```
combineReducers関数で visibilityFilterをtodosとあわせてひとまとめにする。

```javascript:reducers\index.js
import todos from './todo'
import visibilityFilter from './visibilityFilter'
import { combineReducers }  from 'redux';

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


```

index.jsで手動でフィルターを追加できるか確認。

```jsx:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import todoApp from './reducers'
import { createStore } from 'redux';
import App from './components/App'
import {addTodo,toggleTodo,setVisibilityFilter} from './actions'

let store = createStore(todoApp);
store.dispatch(addTodo('Todo 1番目'));
store.dispatch(addTodo('Todo 2番目'));
store.dispatch(addTodo('Todo 3番目'));
store.dispatch(addTodo('Todo 4番目'));
store.dispatch(addTodo('Todo 5番目'));

store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));
console.log(store.getState());

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

```

##実行


```text:ブラウザのコンソール
Object { todos: (5) […], visibilityFilter: "SHOW_COMPLETED" } index.js:17
```
#2.フィルターをViewに反映させてみる。

VisibleTodoList コンテナにgetVisibleTodos関数を追加。
この関数の中で、stateのvisibilityFilterの値に応じて、ネイティブのJavaScriptの配列メソッドfilterを使って、todosにフィルター欲しいTodoだけ濾し分ける。

```javascript:containers\VisibleTodoList.js
import React from 'react';
import { connect } from 'react-redux';
import TodoList from '../components/TodoList'
import { toggleTodo } from '../actions'


function getVisibleTodos(todos, filter_type) {
    switch (filter_type) {
        case 'SHOW_ALL':
            return todos;
        case 'SHOW_ACTIVE':
            return todos.filter(
                (t)=>(!t.completed)
            );
        case 'SHOW_COMPLETED':
        return todos.filter(
            (t)=>(t.completed)
        );
        default:
            break;
    }
}

function mapStateToProps(state) {
    return {
        todos: getVisibleTodos(state.todos,state.visibilityFilter)
    }
}

function mapDispatchToProps(dispatch) {
    return {
        onTodoClick: (id) => { dispatch(toggleTodo(id)) }
    }
}

const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
export default VisibleTodoList;
```
##index.jsの修正

適当に Todoのcompletedを変更して、state.visibilityFilter を SHOW_COMPLETEDにしてみる。

```javascript:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import todoApp from './reducers'
import { createStore } from 'redux';
import App from './components/App'
import {addTodo,toggleTodo,setVisibilityFilter} from './actions'

let store = createStore(todoApp);
store.dispatch(addTodo('Todo 1番目'));
store.dispatch(addTodo('Todo 2番目'));
store.dispatch(addTodo('Todo 3番目'));
store.dispatch(addTodo('Todo 4番目'));
store.dispatch(addTodo('Todo 5番目'));
store.dispatch(toggleTodo(1));
store.dispatch(toggleTodo(3));
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));
console.log(store.getState());

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

```

##実行結果

![名称未設定 1のコピー.jpg](https://qiita-image-store.s3.amazonaws.com/0/76757/b9296835-b326-a7bc-63d6-97e2e3293eaf.jpeg)

#3.ボタンでフィルターの切替をする機能をつける

##ボタンをつくる

SHOW_ACTIVE , SHOW_COMPLETED, SHOW_ALLそれぞれにボタンを用意。
onClick　は上の階層から渡されたdispatchを使える関数

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

const Link = ({ children, onClick }) => (
    <div className={"btn btn-warning w-100 m-2"} onClick={(e) => {
        e.preventDefault();
        onClick();
    }
    }>{children}</div>
)

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

export default Link;
```
##ボタンをラップするコンテナをつくる

LinkコンポーネントをラップするFilteLinkコンテナは、SHOW_ACTIVE , SHOW_COMPLETED, SHOW_ALLのいずれかのfilterプロップスをもたせて、それぞれに応じた関数をLinkコンポーネントにわたす。

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

const mapStateToProps = (state) => {
    return {
        state: state
    }
}

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

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

export default FilterLink;
```
##ボタンを格納するコンポーネントをつくる

Footerコンポーネント。ここで、FilterLinksコンテナにfilterプロップスをもたせる。

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

const Footer = ()=>(
    <div className="d-flex p-2 justify-content-around">
    <FilterLink filter="SHOW_ALL">
      全件
    </FilterLink>
    <FilterLink filter="SHOW_ACTIVE">
      進行中
    </FilterLink>
    <FilterLink filter="SHOW_COMPLETED">
      完了
    </FilterLink>
    </div>
)

export default Footer;
```

##Appコンポーネントの修正

Footerコンポーネント追加

```jsx:components\App.js
import React from 'react';
import VisibleTodoList from '../containers/VisibleTodoList'
import AddTodo from '../containers/AddTodo'
import Footer from "./Footer"

//classNameでBootsrapに関する設定
const App = () => (
    <div>
    <h1 className="text-center alert alert-info ">Todoリスト</h1>
        <VisibleTodoList />
        <AddTodo />
        <Footer />
    </div>
)

export default App;
```

##index.js修正

初期登録の5件のTodoのうち、2件に完了を設定。

```jsx:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import todoApp from './reducers'
import { createStore } from 'redux';
import App from './components/App'
import {addTodo,toggleTodo,setVisibilityFilter} from './actions'

let store = createStore(todoApp);
store.dispatch(addTodo('Todo 1番目'));
store.dispatch(addTodo('Todo 2番目'));
store.dispatch(addTodo('Todo 3番目'));
store.dispatch(addTodo('Todo 4番目'));
store.dispatch(addTodo('Todo 5番目'));
store.dispatch(toggleTodo(1));
store.dispatch(toggleTodo(3));

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

```
##実行結果

![out.gif](https://qiita-image-store.s3.amazonaws.com/0/76757/8bc1771d-0060-1581-831b-1aa869ce3e82.gif)
[ソースコード 07](https://github.com/tkarasuma/JavaScript/tree/master/ws_07)

#4.リンクボタンの有効/無効の表示

ここからは参考にさせて頂いた記事と少し異なる。フィルターが有効になっているボタンはオレンジ色、フィルターが無効になっている場合はボタンの白抜きの灰色枠に表示する。
Linkコンポーネントにactiveというpropsを追加

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

const mapStateToProps = (state,ownProps) => {
    return {
        active: state.visibilityFilter === ownProps.filter
    }
}

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

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

export default FilterLink;
```
Linkコンポーネントでは、受け取ったactiveプロップスに応じて、HTMLを変更

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

const Link = ({ children, onClick, active }) => {
    if (active) {
        return (
            <div className={"btn btn-warning  w-100 m-2"}>{children}</div>
        )
    } else {
        return (
            <div className={"btn btn-outline-secondary w-100 m-2"} onClick={(e) => {
                e.preventDefault();
                onClick();
            }
            }>{children}</div>
        )
    }
}

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

export default Link;
```

##実行結果

![out.gif](https://qiita-image-store.s3.amazonaws.com/0/76757/cf5dfa5a-5f19-3eb4-fafa-6b9e01b1241f.gif)
[ソースコード 08](https://github.com/tkarasuma/JavaScript/tree/master/ws_08)

###補足

マークダウンの編集リクエスト、ありがとうございました。