Riot.js Redux Rails 7 / 8 「完了したTodoをチェック出来るようにする」
はじめに
フロントにRiot.jsとReduxを使いサーバーサイドにRailsを使い、初めに環境構築し、次にReduxの使い方の流れをみて、最終的に簡単なTodoAppを作成する。
環境構築
- Riot.js Redux Rails 1 / 8 「環境構築」
簡単な例でReduxの流れをみる
- Riot.js Redux Rails 2 / 8 「Reduxを使ってtitleを表示させる」
- Riot.js Redux Rails 3 / 8 「formを追加しtitleの変更を可能にする」
- Riot.js Redux Rails 4 / 8 「3で作成したformを、title-formタグとして独立させる」
Todo Appの作成
- Riot.js Redux Rails 5 / 8 「Todoリストを表示する」
- Riot.js Redux Rails 6 / 8 「新たなTodoを追加出来るようにする」
- Riot.js Redux Rails 7 / 8 「完了したTodoをチェック出来るようにする」
- Riot.js Redux Rails 8 / 8 「チェックしてあるタスクを削除出来るようにする」
最終的に完成したTodo App
https://github.com/atfeo/Riot_Redux_Rails
参考
checkbox
チェックボックスを作成。
// ./client/src/tags/task-list.tag <task-list> <ul> <li each={task in this.opts.tasks}> // 追加 <label class={completed: task.isComplete}> <input type="checkbox" id={task.id} checked={task.isComplete} onchange={handleCheck} > {task.name} </label> </li> </ul> <script> handleCheck(e) { this.opts.handlecheck(e.target.id, e.target.checked) } </script> </task-list>
todo-app.tagにマウントされているtask-listタグに処理を追加。
// ./client/src/tags/todo-app.tag <task-list tasks={this.state.tasks} handlecheck={handleTaskCompletionChange} > </task-list> handleTaskCompletionChange(id, isComplete) { store.dispatch(actions.toggleComplete(id, isComplete)) }
サーバーにチェックの状態を送るメソッドなどをactions.jsに追加。
// ./client/src/actions.js module.exports = { loadTasks, addTask, textExists, // 追加 toggleComplete, }; function toggleComplete(id, isComplete) { return (dispatch) => { $.ajax({ url: `/api/tasks/${id}`, type: 'PATCH', dataType: 'json', data: { isComplete }, success: (res) => { dispatch(completeChanged(res.id, res.isComplete)); }, error: (xhr, status, err) => { console.log(`/api/tasks/${id}`, status, err.toString()); }, }); }; } function completeChanged(id, isComplete) { return { type: 'TASK_COMPLETION_CHANGED', data: { id, isComplete } }; }
アクションを送られたときの処理をindex.jsに追加。
// ./client/src/index.js case 'TASK_COMPLETION_CHANGED': { const taskIndex = state.tasks.findIndex((task) => { return task.id == action.data.id; }); const newTasks = [ ...state.tasks.slice(0, taskIndex), Object.assign({}, state.tasks[taskIndex], { isComplete: action.data.isComplete }), ...state.tasks.slice(taskIndex + 1), ]; return Object.assign({}, state, { tasks: newTasks }); }
チェックした時のスタイルを追加。
/* ./app/assets/stylesheets/application.css */ .completed { text-decoration: line-through; color: #ccc; } label { cursor: pointer; }
ここまででviewは完成。
Rails
Migration
サーバー側でisCompleteを扱えるようにする。 ターミナルでctrl-cでforemanを終了させてから
$ rails g migration addIsCompleteToTasks isComplete:boolean
できたマイグレーションファイルに , default: false を追加し
# ./db/migrate/日付_add_is_complete_to_tasks.rb class AddIsCompleteToTasks < ActiveRecord::Migration[5.0] def change add_column :tasks, :isComplete, :boolean, default: false end end
マイグレーションを反映させる。
$ rails db:migrate
これでデータベースのテーブルにisCompleteカラムが追加された。
Controller
次にcontrollerでupdateについての処理を書く。
# ./app/controllers/api/tasks_controller.rb def update @task = Task.find(params[:id]) @task.assign_attributes(task_params) if @task.save render :show, status: :ok else head :unprocessable_entity end end private # isCompleteの追加 def task_params params.permit(:name, :isComplete) end
View(JSON)
JSONがisCompleteも返すようにindex.json.jbuilder、show.json.jbuilderにisCompleteを追加。
# ./app/views/api/tasks/index.json.jbuilder json.tasks(@tasks) { |t| json.extract! t, :id, :name, :isComplete }
# ./app/views/api/tasks/show.json.jbuilder json.extract! @task, :id, :name, :isComplete
再びターミナルで
$ foreman start
でサーバーを開始。
ブラウザでlocalhost:5000を更新してチェックしてもエラーにならないことを確認。
また更新後チェックの状態が維持されることも確認。
サーバーエラー時の処理
チェックの状態が変更された時にサーバー側でエラーが起きると、サーバー側とブラウザ側で矛盾が生じる(checkされているのにisCompleteがfalseまたはその逆)。それを防ぐために、エラー時にチェックを反転させる処理をactions.jsに書く。
// ./client/src/actions.js function toggleComplete(id, isComplete) { return (dispatch) => { $.ajax({ url: `/api/tasks/${id}`, type: 'PATCH', dataType: 'json', data: { isComplete }, success: (res) => { dispatch(completeChanged(res.id, res.isComplete)); }, error: (xhr, status, err) => { // 追加 dispatch(completeChanged(id, !isComplete)); console.log(`/api/tasks/${id}`, status, err.toString()); }, }); }; }
またこの時エラーが起こったことがわかりやすいように、エラーメッセージが表示されるようにする。
まずerror-messageタグを作成。
// ./client/src/tags/error-message.tag <error-message> <div show={this.opts.iserror}> {this.opts.message} </div> <style scoped> :scope div { margin-top: 5px; padding-left: 10px; color: white; background-color: red; } </style> </error-message>
todo-app.tagでerror-messageタグをマウントさせる。
// ./client/src/tags/todo-app.tag </task-form> <error-message message={"Text Message"} iserror={true} > </error-message> <loading-indicator ...>
タグを使うためにindex.jsで読み込む。
// ./client/src/index.js import './tags/error-message.tag';
ブラウザでlocalhost:5000を更新して Text Message というメッセージが表示されていることを確認。
エラーメッセージが表示できるようになったので、エラーが起こった時にだけ表示されるようにする。
showErrorメソッドを作成し、サーバーのエラー時の処理に追加。
// ./client/src/actions.js error: (xhr, status, err) => { // 追加 dispatch(showError('API Error')); dispatch(completeChanged(id, !isComplete)); console.log(`/api/tasks/${id}`, status, err.toString()); }, // 追加 function showError(message) { return { type: 'SHOW_ERROR', data: message }; }
dispatchメソッドでアクションが送られたときの処理を追加。
// ./client/src/index.js case 'SHOW_ERROR': return Object.assign({}, state, { isError: true, errorMessage: action.data });
先程のように固定ではなく、stateの変化に対応できるよう書き換え。
// ./client/src/tags/todo-app.tag <error-message message={this.state.errorMessage} iserror={this.state.isError} > </error-message>
ここでわざとサーバーのurlを間違えたものにしておき、エラーが起こるようにしておく。
ブラウザでlocalhost:5000を更新する。チェックの状態を変更するとエラーが起こりエラーメッセージが表示されること、チェックが反転されることを確認。
次にエラーメッセージが2秒後消えるようにする。
// ./client/src/actions.js error: (xhr, status, err) => { // 書き換え dispatch(tempErrorMessage('API Error')); dispatch(completeChanged(id, !isComplete)); console.log(`/api/tasks/${id}`, status, err.toString()); }, // 追加 function hideError() { return { type: 'HIDE_ERROR' }; } function tempErrorMessage(message) { return (dispatch) => { dispatch(showError(message)); setTimeout(() => { dispatch(hideError()); }, 2000); }; }
新たなアクションの処理を追加。
// ./client/src/index.js case 'HIDE_ERROR': return Object.assign({}, state, { isError: false, errorMessage: '' });
ブラウザでlocalhost:5000を更新する。チェックの状態を変更するとエラーが起こりエラーメッセージが表示され2秒後に消えること、チェックが反転されることを確認。
一応loadTasks()やaddTask()などサーバーと通信している他のメソッドにも
error: (xhr, status, err) => { dispatch(toggleLoading(false)); // 追加 dispatch(tempErrorMessage('API Error'));
このように追加して、エラーメッセージが表示されるようにしておく。
次回はTodoを削除できるようにする。