★前回の記事
タスクの送信状態を表示するように変更します。タスクの各行に送信状態を表すアイコンを表示します。
今回のタスク管理アプリの場合、送信状態を表示することは必須ではありませんが、 チャットアプリのような相手がいて即時性が求められるアプリの場合は、サーバへの送信状況を表示した方がよいでしょう。
方針
タスクの送信状態を、各タスクの横にアイコンを表示することで示します。
- サーバにデータを送信中は送信中のマークをタスクの横に表示します。今回は右上向きの矢印を表示します。
- サーバへのデータ送信が終了後、アイコンを消します。
- エラーが発生した場合はエクスクラメーションマークのアイコンを表示します。
stateオブジェクトの設計
各タスクの状態を持つtodoStatusesオブジェクトを用意します。todoStatusesの各プロパティはキーをtodoIdにすればよさそうです。以下の"aaabbbcccxxxfff"
と"cccdddeeefff"
、"gggeeefffttt"
はtodoIdです。
state = { todoStatuses : { "aaabbbcccxxxfff" : { status : “sending” }, "cccdddeeefff" : { status : “success” }, "gggeeefffttt" : { status : “error” } } }
新規登録時のtodoIdの事前取得
上記のデータ構造を採用する際、 タスクの更新時にはすでにtodoIdが分かっているのでtodoIdをキーとすることは可能です。しかし新規登録では、現在の実装ではtodoIdが分かるのはタスクの登録後です。それでは送信中のステータスを表示することができません。
そこで、新規タスクを登録する前にtodoIdを取得するようにコードを変更します。また、reducerにtodoIdを渡すようにも変更します。
src/actions/todoActions.js
const addTodoRequest = (todoId) => ({ // todoIdを追加 type: ADD_TODO_REQUEST, todoId, // 追加 }) const addTodoSuccess = (todoId) => ({ // todoIdを追加 type: ADD_TODO_SUCCESS, todoId, // 追加 }) const addTodoError = (todoId, err) => ({ // todoIdを追加 type: ADD_TODO_ERROR, todoId, // 追加 err, }) export const addTodo = (uid, text) => { return (dispatch, getState, {getFirebase}) => { if (!uid) { dispatch(notAuthenticatedOnTodoAction()); return; } // dispatch(addTodoRequest()); は下に移動 const firebase = getFirebase(); const id = firebase.push(`todos/${uid}`).key; // #1 dispatch(addTodoRequest(id)); // idを引数に追加 const createdAt = moment().valueOf(); firebase.set(`todos/${uid}/${id}`,{ // #2 completed: false, text, _createdAt : createdAt, _updatedAt : createdAt }) .then(() => { dispatch(addTodoSuccess(id)); // idを引数に追加 }).catch(err => { dispatch(addTodoError(id, err)); // idを引数に追加 }); } }
firebase.push(`todos/${uid}`).key
で新しいデータのkey(id)を取得することができます(#1)。この操作はサーバ通信を行わないため、すぐに結果が返ってきます。- 上で取得したidを使って
todos/${uid}/${id}
にデータをセットします(#2)。push
からset
に変更してあることに注意してくだい。
更新時にtodoIdをactionに渡す
更新時、現在の実装ではタスク名と完了グラグを渡していますが、todoIdを渡すように変更します。
src/actions/todoActions.js
const toggleTodoRequest = (todoId) => ({ // text, completedを削除し、todoIdを追加 type: TOGGLE_TODO_REQUEST, // text, を削除 // completed を削除 todoId, }) const toggleTodoSuccess = (todoId) => ({ // text, completedを削除し、todoIdを追加 type: TOGGLE_TODO_SUCCESS, // text, を削除 // completed を削除 todoId, }) const toggleTodoError = (todoId, err) => ({ // text, completedを削除し、todoIdを追加 type: TOGGLE_TODO_ERROR, // text, を削除 // completed, を削除 todoId, err, }) export const toggleTodo = (uid, id) => { return (dispatch, getState, {getFirebase}) => { if (!uid) { dispatch(notAuthenticatedOnTodoAction()); return; } const firebase = getFirebase(); const state = getState(); const todo = state.firebase.data.todos[uid][id]; dispatch(toggleTodoRequest(id)); // todo.text, !todo.completedを削除し、idを追加 const updatedAt = moment().valueOf(); firebase.update(`todos/${uid}/${id}`, { completed: ! todo.completed, _updatedAt : updatedAt }) .then(() => { dispatch(toggleTodoSuccess(id)); // todo.text, !todo.completedを削除し、idを追加 }).catch(err => { dispatch(toggleTodoError(id, err)); // todo.text, !todo.completedを削除し、idを追加 }); } }
reducerの実装
actionを受け取った時に、todoIdごとにステータスを設定します。
src/reducers/todoStatuses.js
(新規作成)
import { LOCATION_CHANGE_ON_TODOS, LOGOUT_SUCCESS, ADD_TODO_REQUEST, ADD_TODO_SUCCESS, ADD_TODO_ERROR, TOGGLE_TODO_REQUEST, TOGGLE_TODO_SUCCESS, TOGGLE_TODO_ERROR, } from '../actions/' const INITIAL_STATE = {} const todoStatuses = (state = INITIAL_STATE, action) => { switch (action.type) { case ADD_TODO_REQUEST: return { ...state, [action.todoId]: {status : 'sending'}} // #1 case ADD_TODO_SUCCESS: return { ...state, [action.todoId]: {status : 'success'}} case ADD_TODO_ERROR: return { ...state, [action.todoId]: {status : 'error'}} case TOGGLE_TODO_REQUEST: return { ...state, [action.todoId]: {status : 'sending'}} case TOGGLE_TODO_SUCCESS: return { ...state, [action.todoId]: {status : 'success'}} case TOGGLE_TODO_ERROR : return { ...state, [action.todoId]: {status : 'error'}} case LOCATION_CHANGE_ON_TODOS: case LOGOUT_SUCCESS : return INITIAL_STATE default: return state } } export default todoStatuses
- stateに、todoIdをキーとしstatusプロパティを持ったオブジェクトを追加(または上書き)します(#1)。
[action.todoId]
でaction.todoId
が表す値がキーになります。
src/reducers/index.js
import todoStatuses from './todoStatuses' // 追加 export default combineReducers({ firebase: firebaseReducer, auth, notice, todoStatuses, // 追加 visibilityFilter })
送信ステータスの表示
送信ステータスを表示します。
todoStatusをstateから読み込みます。
src/containers/todos/VisibleTodoList.js
const mapStateToProps = ({todoStatuses, visibilityFilter, firebase: {auth, data : {todos, users}}}, {uid}) => { // todoStatusesの追加 return { todos: getVisibleTodos(todos && todos[uid], visibilityFilter), todoStatuses, // 追加 } }
todoStatuses[key]を各todoに渡します。
src/components/todos/TodoList.js
const TodoList = ({todos, isOwnTodos, todoStatuses, onTodoClick, classes}) => { // todoStatusesを追加 // 略 return ( <List> {Object.keys(todos).map( (key) => ( <Todo key={key} isOwnTodos={isOwnTodos} {...todos[key]} todoStatus={todoStatuses[key]} // 追加 onClick={isOwnTodos ? (() => onTodoClick(key)) : (() => {})} /> ) )} </List> ) } TodoList.propTypes = { // 略 todoStatuses: PropTypes.object.isRequired, // 追加 }
送信ステータスによってアイコンを出し分けます。
src/components/todos/Todo.js
import Tooltip from '@material-ui/core/Tooltip' // 追加 import CallMade from '@material-ui/icons/CallMade' // 追加 import Error from '@material-ui/icons/Error' // 追加 // StatusIcon関数の追加 const StatusIcon = (todoStatus) => { if (!todoStatus) { return null; } if (todoStatus.status === 'sending') { return ( <Tooltip title="送信中"> <CallMade /> </Tooltip> ) } if (todoStatus.status === 'error') { return ( <Tooltip title="エラー"> <Error /> </Tooltip> ) } return null } const Todo = ({isOwnTodos, onClick, completed, text, todoStatus}) => ( // todoStatusの追加 <ListItem button={isOwnTodos} onClick={onClick} > {CheckIcon(isOwnTodos, completed)} <ListItemText inset> <span style={ {textDecoration: completed ? 'line-through' : 'none'}}>{text}</span> </ListItemText> {StatusIcon(todoStatus)} {/* 追加 */} </ListItem> ) Todo.propTypes = { // 略 todoStatus: PropTypes.shape({ // 追加 status: PropTypes.oneOf(['sending', 'success', 'error']).isRequired }) }
実行結果
実行結果です。
送信中は右上向きの矢印とマウスオーバー時に送信中
のツールチッブが表示されます。
エラー時はエクスクラメーションマークとマウスオーバー時にエラー
のツールチッブが表示されます。
(通常の操作ではエラーが発生しないので、少しソースを変更してエラーを出しています。)
以上でタスクの送信状態を表示することができました。
参考
- List React component - Material-UI
- react-redux-firebaseで、pushの自動生成key(ID)を事前に取得 - Javaエンジニア、React+Redux+Firebaseでアプリを作る
★次回の記事
★目次