LoginSignup
12
9

More than 5 years have passed since last update.

React + ReduxでTodoアプリを作ってみよう!『Filter Todo編』

Last updated at Posted at 2019-05-06

概要

前回の記事までは、Todoを追加する『Add Todo』と、Todoの未・済を切り替える『Toggle Todo』の機能を実装して参りました。今回は、選択したフィルタによって表示するTodoを変更する『Filter Todo』の機能を実装していきたいと思います!

『Add Todo』に関してはこちら
『Toggle Todo』に関してはこちら

完成品

『All』,『Active』,『Completed』のリンクを押すことで、ページに表示されるTodoが変わります!
ezgif.com-optimize (3).gif

Action Creatorの作成

新たにsetVisibilityFilterを追加します。ここでは、フィルター(SHOW_COMPLETEDなど)を受け取り、typeとfilterを返します。

src/actions/index.js
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';

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

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

export const toggleTodo = id => {
  return {
    type: TOGGLE_TODO,
    id,
    //index: index
  };
};

export const setVisibilityFilter = filter => {
  return {
    type: SET_VISIBILITY_FILTER,
    filter,
    // filter: filter
  };
};

reducerの作成

初期stateをSHOW_ALLとし、action.typeSET_VISIBILITY_FILTERの際にaction.filterを新しいstateとして返します。

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.jsにてcombineReducers関数に加えましょう!

src/reducers/index.js
import {combineReducers} from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';

const todoApp = combineReducers({todos, visibilityFilter});

export default todoApp;

動作確認

src/index.jsにて、正しくデータが格納されるか手動で確認してみましょう!

src/index.js
import {addTodo, toggleTodo, setVisibilityFilter} from './actions';

console.log(store.getState()); /// "SHOW_ALL"
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));
console.log(store.getState()); /// "SHOW_COMPLETED"

スクリーンショット 2019-05-06 20.35.44.png

ここまでで、Action Creatorとreducerを作成し、フィルターの値をstoreに格納することができました。

次からは、フィルターの値によってviewの表示を変更できるようにしましょう!

VisibleTodoListを修正する

todos.filter()のように配列のメソッドのフィルタを用いることで、todoのcompleted属性によって、新たに作成した配列を返します。

src/containers/VisibleTodoList.js
import {connect} from 'react-redux';
import TodoList from '../components/TodoList';
import {toggleTodo, VisibilityFilters} from '../actions';

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case VisibilityFilters.SHOW_ALL:
      return todos;
    case VisibilityFilters.SHOW_COMPLETED:
      return todos.filter(todo => todo.completed);  //todos.filter()は配列のメソッドのフィルタ 
    case VisibilityFilters.SHOW_ACTIVE:
      return todos.filter(todo => !todo.completed); //todos.filter()は配列のメソッドのフィルタ
  }
};

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

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

const VisibleTodoList = connect(
  mapStateToPorops,
  mapDispatchToProps
)(TodoList);

export default VisibleTodoList;

先ほどと同様にsrc/index.jsにて手動で動作確認をしましょう!

src/index.js
import {addTodo, toggleTodo, setVisibilityFilter} from './actions';

store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));

Linkを作成する

表示したいTodoの種類をLinkをクリックすることによって表示できるようにします。

まずは、とりあえずLinkを表示させましょう!

props.childrenはコンポーネントの中身を取得できます。Linkコンポーネントを使うときの、<Link>xxx</Link>xxxです。

src/components/Link.js
import React from 'react';
import PropTypes from 'prop-types';

const Link = ({children, onClick}) => {
  return (
    <a href="#">{children}</a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Link;

LinkコンポーネントはFooterコンポーネントで使用します。

src/components/Footer.js
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = () => {
  return (
    <p>
      Show: <Link>All</Link>
      {', '}
      <Link>Active</Link>
      {', '}
      <Link>Completed</Link>
    </p>
  );
};

export default Footer;

FooterコンポーネントはAppコンポーネントで表示します!

src/component/App.js
import React from 'react';
import VisibleTodoList from '../containers/VisibleTodoList';
import AddTodo from '../containers/AddTodo';
import Footer from './Footer';

const App = () => {
  return (
    <div className="App">
      <AddTodo />
      <VisibleTodoList />
      <Footer />
    </div>
  );
};

export default App;

これでリンクの表示が完了です。

リンクをクリックしたときにフィルターの値を変える

リンクをクリックした際にフィルターの値(SHOW_ALLなど)を変えるには、リンクをクリックした際に、dispatch(setVisibilityFilter())を呼び出せるようにします。
connect関数を使って、propsにdispatchを渡せるようにしましょう!

src/containers/FilterLink.js
import {connect} from 'react-redux';
import {setVisibilityFilter} from '../actions';
import Link from '../components/Link';

const mapStateToProps = (state, ownProps) => {
  return {state: state};
};

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

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link);

export default FilterLink;

src/components/Footer.jsでLinkとmapStateToProps,mapDispatchToProps
をconnectさせた『FilterLink』を使います。
この際に、mapDispatchToPropsのonClick関数にフィルターの値を渡すためfilter="SHOW_ALL"のように記述します。

src/components/Footer.js
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = () => {
  return (
    <p>
      Show: <FilterLink filter="SHOW_ALL">All</FilterLink>
      {', '}
      <FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
      {', '}
      <FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>
    </p>
  );
};

export default Footer;

Linkをクリックした際にonClick関数を呼ぶ

『preventDefault』は『デフォルトの動作を発生させない』という意味です。今回はaタグの中で使っているのですが、これは『aタグのherfで指定されたURLへ遷移する動作を発生させない』という意味を持っております。

src/components/Link.js
import React from 'react';
import PropTypes from 'prop-types';

const Link = ({children, onClick}) => {
  return (
    <a
      href="#"
      onClick={e => {
        e.preventDefault();
        onClick();
      }}>
      {children}
    </a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired,
};

export default Link;

これで、Linkを押すとviewの表示が切り替わる動作を実装することができました。
(Linkクリック→onClick関数が呼び出される→dispatch(setVisibilityFilter())が呼び出される→storeに保存されているフィルターの値が更新→viewが書き換わる)

現在activeなリンクを押せなくする

現在activeなリンクを押せなくするために、activeなリンクをただのテキストに変更する機能を実装いたします。

ここでは,Linkコンポーネントの現在の状態を知るためにprops.activeとしてデータを渡します。

src/containers/FilterLink.js
const mapStateToProps = (state, ownProps) => {
  return {active: ownProps.filter === state.visibilityFilter};
};

そして、activeの状態によってテキストを返すか、リンクを返すかをsrc/components/Link.jsにて判断いたします。

src/components/Link.js
import React from 'react';
import PropTypes from 'prop-types';

const Link = ({active, children, onClick}) => {
  if (active) {
    return <span>{children}</span>;
  }

  return (
    <a
      href="#"
      onClick={e => {
        e.preventDefault();
        onClick();
      }}>
      {children}
    </a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired,
};

export default Link;

以上で、『Filter Todo』の実装は完了です!

公式のBasicTutorial完了

これで公式のBasicTutorialと同じ機能が実装できたように思います。

ezgif.com-optimize (4).gif

リファレンス

12
9
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
12
9