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追加
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関数
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とあわせてひとまとめにする。
import todos from './todo'
import visibilityFilter from './visibilityFilter'
import { combineReducers } from 'redux';
const todoApp = combineReducers({todos,visibilityFilter});
export default todoApp;
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だけ濾し分ける。
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にしてみる。
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')
);
実行結果
3.ボタンでフィルターの切替をする機能をつける
ボタンをつくる
SHOW_ACTIVE , SHOW_COMPLETED, SHOW_ALLそれぞれにボタンを用意。
onClick は上の階層から渡されたdispatchを使える関数
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コンポーネントにわたす。
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プロップスをもたせる。
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コンポーネント追加
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件に完了を設定。
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')
);
実行結果
4.リンクボタンの有効/無効の表示
ここからは参考にさせて頂いた記事と少し異なる。フィルターが有効になっているボタンはオレンジ色、フィルターが無効になっている場合はボタンの白抜きの灰色枠に表示する。
Linkコンポーネントにactiveというpropsを追加
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を変更
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;
実行結果
補足
マークダウンの編集リクエスト、ありがとうございました。