★前回の記事
前回までで他のユーザのタスクは閲覧できて、自身のタスクは閲覧・編集できるようになりました。しかし、まだバグが2つ残っているので、そのうち1つを修正します。
バグ再現手順
- ログインします
- ログインユーザ以外のタスクを表示します。
- “タスクを編集する”を押して、ログインユーザのタスクを表示します。
- タスクをクリックして、”xxxのステータスを(未)完了に変更しました”と表示します。
- ブラウザバックします。
- ログインユーザ以外のタスク”xxxのステータスを(未)完了に変更しました”と表示されます。
修正方針
ざっと2つの修正方法が思いつきます。
- ログインユーザ以外のタスク一覧を表示している時は、NoticeForTodoコンポーネントを表示しない
- 画面遷移ごとにstate.todos.noticeをクリアする
1番目の方法は実装しやすいですが、いくつかの画面遷移後にまたログインユーザのタスク一覧画面に戻ってきた時に、前回の更新メッセージが表示されてしまいます。それでは根本的な解決とは言えなさそうです。また、NoticeForTodoコンポーネントとstate.todos.noticeはログインユーザのタスクに関係なく使用したいので、今回は、2番目の方法を採用します。
ついでに、画面遷移時に、VisibilityFilterも初期化します。
実装方針
実装方法を紹介する前に、react-routerのバージョン3から4への変更点について触れておきます。
react-router v3のonEnter, onChange, onLeave
react-routerのバージョン3には、RouteコンポーネントにonEnterとonChange, onLeaveというpropsを渡すと、component propsに渡したコンポーネントがそれぞれマウントされる時、パスが変更された時、アンマウントされる時に呼ばれる関数を指定することができました。しかしバージョン4で廃止されています。
react-router v4での実装方法
react-router v4では、ページの遷移をcomponentWillMount/componentDidMount、componentWillReceiveProps、componentWillUnmountで検知します。
同一コンポーネント内でのURLの変更を検知するのには、公式のドキュメント(react-router/history)に書いてある通り、componentWillReceivePropsを使います。
componentWillReceiveProps(nextProps) { const locationChanged = nextProps.location !== this.props.location; }
実装
TodoComponentがマウントされた時とURLが変更された時に、locationChangeOnTodos actionをdispatchします。
先にactionとreducerを実装します。
src/action/index.js
// todo actions export const LOCATION_CHANGE_ON_TODOS = 'LOCATION_CHANGE_ON_TODOS' // 追加
src/action/todoAction.js
import { LOCATION_CHANGE_ON_TODOS, //他のimportは省略 } from './' export const locationChangeOnTodos = () => ({ // 追加 type: LOCATION_CHANGE_ON_TODOS })
src/reducers/todos.js
import { LOCATION_CHANGE_ON_TODOS, //他のimportは省略 } from '../actions/' // 中略 const todos = (state = {}, action) => { switch (action.type) { // 中略 case LOCATION_CHANGE_ON_TODOS: // 追加 case LOGOUT_SUCCESS : return {} default: return state } }
src/reducers/visibilityFilter.js
import { LOCATION_CHANGE_ON_TODOS, // 他のimportは省略 } from '../actions/' // 中略 const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter case LOCATION_CHANGE_ON_TODOS: // 追加 case LOGOUT_SUCCESS: return VisibilityFilters.SHOW_ALL default: return state } }
Viewを実装します。マウントされる時とURLの、/users/:id/todos
の:id
が変更された時に、locationChangeOnTodos
actionをdispatchします。
src/components/TodoComponent.js
// 前略 import { locationChangeOnTodos } from '../actions/todoActions' // #1 class TodoComponent extends React.Component { // #2 componentWillMount() { // #3 this.props.locationChange() } componentWillReceiveProps(nextProps) { // #4 if(nextProps.location !== this.props.location) { // #5 this.props.locationChange() } } render() { // #6 const {isOwnTodos, match: { params: {uid}}} = this.props; return ( <div> {isOwnTodos && <AddTodo uid={uid} />} <NoticeForTodo /> <VisibleTodoList uid={uid} isOwnTodos={isOwnTodos} /> <Footer /> </div> ) } } // 中略 TodoComponent.propTypes = { // 中略 locationChange: PropTypes.func.isRequired // #7 } // 中略 const mapDispatchToProps = (dispatch) => ({ locationChange: () => dispatch(locationChangeOnTodos()) // #8 }) TodoComponent = connect( mapStateToProps, mapDispatchToProps // #9 )(TodoComponent)
locationChangeOnTodos
をimportします(#1)。- TodoComponentを、関数コンポーネント(function component)から
React.Component
を拡張したクラスに変更します(#2)。 componentWillMount
でlocationChange
を呼び出します(#3)。呼び出すことで、locationChangeOnTodos
actionがdispatchされます。componentWillReceiveProps
を定義します(#4)。この関数は、propsが変更される時に呼び出されます。componentWillReceiveProps
では、前後のprops.location
の値を比較します(#5)。URLが変更されていればtrueになります。その場合にlocationChange
を呼び出します。- 関数コンポーネントからクラスに変更になったため、
render
メソッドを記述します(#6)。propsの受け取り方も変更されています。 locationChange
に対応するPropTypeを追加します(#7)。locationChangeOnTodos
をdispatchする関数を、locationChange
という名前で下位コンポーネントに渡します(#8)。mapDispatchToProps
をconnect
の引数に追加します(#9)。
動作確認
- ログインします
- ログインユーザ以外のタスクを表示します。
- “タスクを編集する”を押して、ログインユーザのタスクを表示します。
- タスクをクリックして、”xxxのステータスを(未)完了に変更しました”と表示します。
- ブラウザバックします。
- ログインユーザ以外のタスク”xxxのステータスを(未)完了に変更しました”と表示されます。
Noticeが消えて、うまく動きました。
visibilityFilterの動作確認をします。
ページ遷移時にfilterを全て表示(SHOW_ALL)にできました。
以上で、ページ遷移時にactionをdispatchして、noticeとvisibilityFilterを変更することができました。
更新メッセージの表示と消去について
シングルページアプリケーションの場合、リンクをクリックしてページ遷移しても、状態は初期化されません。ページの再読み込みが必要なくなるのでページの表示が高速になる一方、状態も更新されないので、きちんと値を管理しないと意図しない表示になります。
今回のアプリケーションでは、更新完了時に”完了しました”とメッセージを表示しました。 開発中は、”メッセージが表示されること”に気が向かってしまい、”メッセージが消えること”まで気を使えないことが多くあります。
更新のメッセージについては、一定時間のみ画面に表示し、自動で消えるという実装をすると、別画面で更新メッセージが表示されることを防げます。設計段階でそのような挙動を盛り込むことを検討するとよいと思います。
★次回の記事
★目次