Reduxのstateをフィルタリング、ソートする。

Tatsuya Asami
16 min readNov 6, 2018

--

【所要時間】

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の状態がわかりやすい。

これをベースにフィルター、ソート機能を加える。

https://stackoverflow.com/questions/34003553/redux-what-is-the-correct-way-to-filter-a-data-array-in-reducer/34011774

考え方としては

  1. ソートやフィルタはReact側で行う。
  2. 今回のソートやフィルタの条件は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リスト

のコードが複雑に見えて理解に時間がかかったが、やっていることは今回作成したのと同じだった。

--

--

Tatsuya Asami
Tatsuya Asami

Written by Tatsuya Asami

Front end engineer. React, TypeScript, Three.js

No responses yet