★前回の記事
yucatio.hatenablog.com
タスクの送信状態を表示するように変更します。タスクの各行に送信状態を表すアイコンを表示します。
今回のタスク管理アプリの場合、送信状態を表示することは必須ではありませんが、
チャットアプリのような相手がいて即時性が求められるアプリの場合は、サーバへの送信状況を表示した方がよいでしょう。
方針
タスクの送信状態を、各タスクの横にアイコンを表示することで示します。
- サーバにデータを送信中は送信中のマークをタスクの横に表示します。今回は右上向きの矢印を表示します。
- サーバへのデータ送信が終了後、アイコンを消します。
- エラーが発生した場合はエクスクラメーションマークのアイコンを表示します。
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) => ({
type: ADD_TODO_REQUEST,
todoId,
})
const addTodoSuccess = (todoId) => ({
type: ADD_TODO_SUCCESS,
todoId,
})
const addTodoError = (todoId, err) => ({
type: ADD_TODO_ERROR,
todoId,
err,
})
export const addTodo = (uid, text) => {
return (dispatch, getState, {getFirebase}) => {
if (!uid) {
dispatch(notAuthenticatedOnTodoAction());
return;
}
const firebase = getFirebase();
const id = firebase.push(`todos/${uid}`).key;
dispatch(addTodoRequest(id));
const createdAt = moment().valueOf();
firebase.set(`todos/${uid}/${id}`,{
completed: false,
text,
_createdAt : createdAt,
_updatedAt : createdAt
})
.then(() => {
dispatch(addTodoSuccess(id));
}).catch(err => {
dispatch(addTodoError(id, err));
});
}
}
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) => ({
type: TOGGLE_TODO_REQUEST,
todoId,
})
const toggleTodoSuccess = (todoId) => ({
type: TOGGLE_TODO_SUCCESS,
todoId,
})
const toggleTodoError = (todoId, err) => ({
type: TOGGLE_TODO_ERROR,
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));
const updatedAt = moment().valueOf();
firebase.update(`todos/${uid}/${id}`, {
completed: ! todo.completed,
_updatedAt : updatedAt
})
.then(() => {
dispatch(toggleTodoSuccess(id));
}).catch(err => {
dispatch(toggleTodoError(id, err));
});
}
}
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'}}
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}) => {
return {
todos: getVisibleTodos(todos && todos[uid], visibilityFilter),
todoStatuses,
}
}
todoStatuses[key]を各todoに渡します。
src/components/todos/TodoList.js
const TodoList = ({todos, isOwnTodos, todoStatuses, onTodoClick, classes}) => {
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'
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}) => (
<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
})
}
実行結果
実行結果です。
送信中は右上向きの矢印とマウスオーバー時に送信中
のツールチッブが表示されます。
エラー時はエクスクラメーションマークとマウスオーバー時にエラー
のツールチッブが表示されます。
(通常の操作ではエラーが発生しないので、少しソースを変更してエラーを出しています。)
以上でタスクの送信状態を表示することができました。
参考
★次回の記事
yucatio.hatenablog.com
★目次
yucatio.hatenablog.com