LoginSignup
1
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-30

ReduxのサンプルのTodoリストを真似てみる。Part.3の続きです。

0.留意点

  • Redux ExampleのTodo Listをはじめからていねいに(3)を参考にさせて頂いて、Reduxのコードを書いた。
  • 初心者の覚書です。
  • 自分の環境で動くように参考にしたコードを適当に修正している。
  • Windows10 64bit , PowerShellなどで動かしている。
  • 見栄えを若干よくする為にbootstrap4を利用している。

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

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

actionCreator追加

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関数 

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とあわせてひとまとめにする。

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で手動でフィルターを追加できるか確認。

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')
);

実行

ブラウザのコンソール
Object { todos: (5) […], visibilityFilter: "SHOW_COMPLETED" } index.js:17

2.フィルターをViewに反映させてみる。

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

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にしてみる。

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

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

ボタンをつくる

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

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コンポーネントにわたす。

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プロップスをもたせる。

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コンポーネント追加

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件に完了を設定。

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
ソースコード 07

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

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

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を変更

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
ソースコード 08

補足

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0