サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

React + Redux入門 04: リスト項目のフィルタを加える


React + Redux入門 03: 項目の処理済みと未処理でスタイルを変える」では、リストの各項目にチェックサインをつけて、未処理の項目はテキストのスタイルが変わるようにしました。さらに、項目のリスト表示を、すべてのほか処理済みまたは未処理だけに切り替えられるフィルタの機能を加えましょう。

01 フィルタのAction Creatorを加える

まずは、モジュールsrc/actions/index.jsにAction CreatorのsetVisibilityFilterを加えます。併せてオブジェクトVisibilityFiltersに定めたのは、Actionオブジェクトのプロパティfilterがとる定数です。

src/actions/index.js

export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';

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

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

02 3つのフィルタボタンが備わったコンポーネントをつくる

新たにつくるモジュールsrc/components/Footer.jsは、3つのフィルタボタンを備えるフッタのコンポーネントです(コード001)。ボタンは子コンポーネント(Link)としてこのあと定め、それぞれのフィルタ定数を属性filterに与えます。

コード001■src/components/Footer.js


import React from 'react';
import Link from './Link';
import { VisibilityFilters } from '../actions';

const Footer = () => (
	<div>
		<span>Show: </span>
		<Link filter={VisibilityFilters.SHOW_ALL}>
			All
		</Link>
		<Link filter={VisibilityFilters.SHOW_ACTIVE}>
			Active
		</Link>
		<Link filter={VisibilityFilters.SHOW_COMPLETED}>
			Completed
		</Link>
	</div>
);

export default Footer;

フィルタボタンとして新規に加えるモジュールsrc/components/Link.jsの記述は、以下のコード002のとおりです。connect()関数の第1および第2引数に渡す関数(mapStateToProps()とmapDispatchToProps())は、いずれも第2引数(ownProps)としてラッパーコンポーネントに与えられたプロパティの参照を受け取ります。このプロパティから取り出すのは、前掲コード001で親コンポーネントから与えられたfilter属性の値(フィルタ定数)です。

ボタンがクリックされると、フィルタ定数を納めたActionオブジェクトがStoreに送られます。そして、定数がフィルタボタンと一致するとdisabled属性がtrueになって、スタイルが変わる仕組みです。

コード002■src/components/Link.js


import React from 'react';
import { connect } from 'react-redux';
import { setVisibilityFilter } from '../actions';

const Link = ({ active, children, onClick }) => (
	<button
		onClick={onClick}
		disabled={active}
		style={{
			marginLeft: '4px',
		}}
	>
		{children}
	</button>
);

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

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

export default connect(
	mapStateToProps,
	mapDispatchToProps
)(Link);

まだReducerがつくられていないので、スタイルは変わりません。それでも、アプリケーションのモジュールsrc/components/App.jsにフッタ(Footer)を組み込めば、3つのフィルタボタンが表れるでしょう(図001)。

src/components/App.js

import Footer from './Footer';

const App = () => (
	<div>

		<Footer />
	</div>
);

図001■3つのフィルタボタンが加わる

図001

03 フィルタ切り替えのReducerをつくる

新しいモジュールsrc/reducers/visibilityFilter.jsとして、フィルタ切り替えのReducerをつぎのコード003のように定めましょう。送られてきたActionから新たに選ばれたフィルタ定数(filter)を取り出して返します。

コード003■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.jscombineReducers()メソッドの引数オブジェクトに加えましょう。これで、選んだフィルタボタンのスタイルが変わるはずです(図002)。

src/reducers/index.js

import visibilityFilter from './visibilityFilter';

export default combineReducers({

	visibilityFilter
});

図002■選ばれたフィルタボタンのスタイルが変わる

図002

04 表示するリスト項目をフィルタで切り替える

項目をフィルタリングする関数(getVisibleTodos)を加えるのは、モジュールsrc/components/TodoList.jsです。mapStateToProps()から呼び出して、選択されたフィルタに応じた項目を配列にしてコンポーネントのtodosプロパティに与えます。

src/components/TodoList.js

// import { toggleTodo } from '../actions';
import { toggleTodo, VisibilityFilters } from '../actions';

const getVisibleTodos = (todos, filter) => {
	switch (filter) {
		case VisibilityFilters.SHOW_ALL:
			return todos
		case VisibilityFilters.SHOW_COMPLETED:
			return todos.filter(t => t.completed)
		case VisibilityFilters.SHOW_ACTIVE:
			return todos.filter(t => !t.completed)
		default:
			throw new Error('Unknown filter: ' + filter)
	}
}

const mapStateToProps = (state) => ({
	// todos: state.todos
	todos: getVisibleTodos(state.todos, state.visibilityFilter)
});

これで、フィルタボタンにより未処理や表示済みなど、項目のリストが切り替えられるでしょう(図003)。これまで書き替えた各モジュールのコード全体は、以下のコード004から007に掲げたとおりです。すべてのファイルのコードと実際の動きは、CodePenに上げたサンプル001でお確かめください。

図003■処理済みと未処理の項目表示をフィルタで切り替える

図003左
   
図003右

コード004■src/actions/index.js


export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';

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

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

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

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

コード005■src/components/App.js


import React from 'react';
import AddTodo from './AddTodo';
import TodoList from './TodoList';
import Footer from './Footer';
import '../App.css';

const App = () => (
	<div>
		<AddTodo />
		<TodoList />
		<Footer />
	</div>
);

export default App;

コード006■src/reducers/index.js


import { combineReducers } from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';

export default combineReducers({
	todos,
	visibilityFilter
});

コード007■src/components/TodoList.js


import React from 'react';
import { connect } from 'react-redux';
import { toggleTodo, VisibilityFilters } from '../actions';
import Todo from './Todo';

const TodoList = ({ todos, toggleTodo }) => (
	<ul className="todo-list">
		{todos.map((todo) =>
			<Todo
				key={todo.id}
				{...todo}
				onClick={() => toggleTodo(todo.id)}
			/>
		)}
	</ul>
);

const getVisibleTodos = (todos, filter) => {
	switch (filter) {
		case VisibilityFilters.SHOW_ALL:
			return todos
		case VisibilityFilters.SHOW_COMPLETED:
			return todos.filter(t => t.completed)
		case VisibilityFilters.SHOW_ACTIVE:
			return todos.filter(t => !t.completed)
		default:
			throw new Error('Unknown filter: ' + filter)
	}
}

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

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

export default connect(
	mapStateToProps,
	mapDispatchToProps
)(TodoList);

サンプル001■react-redux-todos-04

React + Redux入門


作成者: 野中文雄
作成日: 2019年8月15日


Copyright © 2001-2019 Fumio Nonaka.  All rights reserved.