Reduxのstateをフィルタリング、ソートする。
【所要時間】
11時間14分(2018年11月5,6日)
【概要】
- Stateデータをいい感じにソート、フィルタリングする。
完成レポジトリ
https://github.com/astatsuya/reduxtest/tree/master
【要約・学んだこと】
まずは簡単にstateの状態がわかるようにする。addInfoというアクション、
Redux
action
// src/redux/actions.jsexport const ADD_INFO = 'ADD_INFO';export const addInfo = info => ({
type: ADD_INFO,
payload: info,
});
reducer
// src/redux/reducer.jsimport { combineReducers } from 'redux';
import { ADD_INFO } from './actions';const initialState = {
book: [
{
title: '1st ',
age: 100,
},
{
title: '2nd ',
age: 300,
},
{
title: '3rd ',
age: 200,
},
],
};const books = (state = initialState, action) => {
switch (action.type) {
case ADD_INFO:
return {
...state,
book: [
...state.book,
action.payload,
],
};
default:
return state;
}
};const rootReducer = combineReducers({
books,
});export default rootReducer;
React
// src/App.jsimport React from 'react';
import './App.css';
import Dispatch from './components/Dispatch';
import GetState from './components/GetState';const App = () => (
<div>
Redux test
<Dispatch />
<GetState />
</div>
);export default App;
Dispatchコンポーネント
// src/components/Dispatch.jsimport React from 'react';
import { connect } from 'react-redux';
import { addInfo } from '../redux/actions';const mapDispatchToProps = dispatch => {
return {
addInfo: info => dispatch(addInfo(info)),
};
};const Connectedbutton = ({ addInfo }) => {
const dispatchContents = {
title: 'dispatch ',
age: 432,
};
return (
<div>
<br />
addInfo
<button onClick={() => addInfo(dispatchContents)}>
add title
</button>
</div>
);
};
GetStateコンポーネント
// src/components/GetState.jsimport React from 'react';
import { connect } from 'react-redux';const mapStateToProps = state => {
return {
books: state.books.book,
};
};const Connectedbutton = ({ books }) => {
return (
<div>
<br />
stateをそのまま表示
<p>
{books.map(book => book.title)}
</p>
<p>
{books.map(book => book.age)}
</p>
</div>
);
};
const GetState = connect(mapStateToProps)(Connectedbutton);export default GetState;
これでStateの状態がわかりやすい。
これをベースにフィルター、ソート機能を加える。
考え方としては
- ソートやフィルタはReact側で行う。
- 今回のソートやフィルタの条件はReduxで管理する。
この2点。
- 実際に表示する内容を取り扱うbooks
- ソートする条件をsort
- フィルタリングする条件をfilter
わかりやすいようにこれらのactionとreducerをそれぞれ作る。
// src/redux/actions.jsexport const ADD_INFO = 'ADD_INFO';
export const SORT = 'SORT';
export const FILTER = 'FILTER';export const addInfo = info => ({
type: ADD_INFO,
payload: info,
});export const sort = sortcase => ({
type: SORT,
sort: sortcase,
});export const filter = filtercase => ({
type: FILTER,
filter: filtercase,
});
sortとfilterのreducerは、タブで選択されているソート、フィルタリングの条件を保持するだけ。
// src/redux/reducer.js...export const sort = (state = { sort: 'ALL' }, action) => {
switch (action.type) {
case SORT:
return action.sort;
default:
return state;
}
};export const filter = (state = { filter: 'ALL' }, action) => {
switch (action.type) {
case FILTER:
return action.filter;
default:
return state;
}
};const rootReducer = combineReducers({
books,
sort,
filter,
});export default rootReducer;
Reduxでのソーティング、フィルタリングとは関係ないが、React内での受け渡しも色々慣れたいので、わざわざ親子、兄弟関係を作った。(そのためレイアウトがしにくくなった。)
これらからソート、フィルタリングの条件をDispatchする。
// src/components/FilterParent.jsimport React from 'react';
import { connect } from 'react-redux';
import { sort, filter } from '../redux/actions';
import FilterCondition from './FilterCondition';
import FilterButton from './FilterButton';const mapDispatchToProps = (dispatch) => {
return {
sortChange: sortcase => dispatch(sort(sortcase)),
filterChange: filtercase => dispatch(filter(filtercase)),
};
};class FilterParent extends React.Component {
constructor(props) {
super(props);this.state = {
sort: 'ALL',
filter: 'ALL',
};this.changeCondition = this.changeCondition.bind(this);
this.handleClick = this.handleClick.bind(this);
this.filterChangeCondition = this.filterChangeCondition.bind(this);
this.filterClick = this.filterClick.bind(this);
}changeCondition(event) {
this.setState({
sort: event,
});
}filterChangeCondition(event) {
this.setState({
filter: event,
});
}handleClick() {
const sort = this.state.sort;
this.props.sortChange(sort)
}filterClick() {
const filter = this.state.filter;
this.props.filterChange(filter)
}render() {
return (
<div>
<FilterCondition
sortChange={this.changeCondition}
filterChange={this.filterChangeCondition}
/>
<FilterButton
sortClick={this.handleClick}
sortContents={this.state.sort}
filterClick={this.filterClick}
filterContents={this.state.filter}
/>
</div>
);
}
}const FilterLink = connect(
null,
mapDispatchToProps,
)(FilterParent);export default FilterLink;
ここでソート、フィルタリングの条件を指定する。
// src/components/FilterCondition.jsimport React from 'react';class FilterCondition extends React.Component {
constructor(props) {
super(props);this.sortChange = this.sortChange.bind(this);
this.filterChange = this.filterChange.bind(this);
}sortChange(event) {
const sortContents = event.target.value;this.props.sortChange(sortContents);
}filterChange(event) {
const filterContents = event.target.value;this.props.filterChange(filterContents);
}render() {
return (
<div>
Sort:
<select id="sortContents" onChange={this.sortChange}>
<option value="ALL">ID</option>
<option value="BOOKS">BOOKS</option>
<option value="AGE">AGE</option>
</select>
{" "}
filter:
<select id="filterContents" onChange={this.filterChange}>
<option value="ALL">ALL</option>
<option value="aaa ">aaa </option>
<option value="bbb ">bbb</option>
</select>
</div>
);
}
}export default FilterCondition;
ここでクリックするボタンを作る。親コンポーネントのクリックイベントを発動させる。
// src/components/FilterButton.jsimport React from 'react';const FilterButton = ({ sortClick, filterClick }) => {
return (
<div>
<button type="button" onClick={sortClick}>Sort</button>
{' '}
<button type="button" onClick={filterClick}>Filter</button>
</div>
);
};export default FilterButton;
これでセレクターからソート、フィルタリングの条件を選ぶことができる。
続いてReduxのStateを受け取り、ソート、フィルタリング後の表示結果を変化させる。
// src/components/GetState.jsimport React from 'react';
import { connect } from 'react-redux';const changeViewState = (state, sort) => {
switch (sort) {
case 'ALL':
return state.sort((a, b) => a.id - b.id);
case 'BOOKS':
return state.sort((a, b) => {
const titleA = a.title.toUpperCase();
const titleB = b.title.toUpperCase();
if (titleA < titleB) {
return -1;
}
if (titleA > titleB) {
return 1;
}
return 0;
});
case 'AGE':
return state.sort((a, b) => a.age - b.age);
default:
return state;
}
};const changeFilter = (state, filtered) => {
switch (filtered) {
case 'ALL':
return state;
case 'aaa ':
return state.filter(a => a.title === 'aaa ');
case 'bbb ':
return state.filter(a => a.title === 'bbb ');
default:
return state;
}
};const mapStateToProps = (state) => {
return {
sorted: changeViewState(state.books.book, state.sort),
filtered: changeFilter(state.books.book, state.filter),
state,
};
};const Connectedbutton = ({ sorted, filtered }) => {
return (
<div>
sortだけした結果
<p>
{sorted.map(book => book.title)}
</p>
<p>
{sorted.map(book => book.age)}
</p>
sortしてfilterした結果
<p>
{filtered.map(book => book.title)}
</p>
<p>
{filtered.map(book => book.age)}
</p>
</div>
);
};const GetState = connect(mapStateToProps)(Connectedbutton);export default GetState;
sortとfilterをする関数を作成し、ReduxのStateを受け取るmapStateToPropsで部分で、変化させる。
これで下記のようにStateの表示内容を変化させることができる。
前回フィルタリングをした際との大きな違いは、Reduxを経由してソート、フィルタリングの条件を変えたという点。このアプリでは直接Reactで切り替えた方が楽だが、構成が複雑になった場合は、このやり方の方でできる。
【わからなかったこと】
const mapStateToProps = (state) => {
return {
sorted: changeViewState(state.books.book, state.sort),
filtered: changeFilter(state.books.book, state.filter),
state,
};
};
ここにstateを置くと問題なく動作するが、取り除くとsortをdispatchしても画面が切り替わらない。consoleで確かめると、ちゃんと変わっているし、sortをdispatchした後にaddInfoやfilterをdispatchすると切り替わる。
【感想】
Redux公式のサンプルのTodoリスト
のコードが複雑に見えて理解に時間がかかったが、やっていることは今回作成したのと同じだった。