Learn プログ

-マナビメモ-

React Redux Rails 5 / 5 「チェックしてあるタスクを削除出来るようにする」

はじめに

Riot.js Redux Railsシリーズで作ったTodoアプリのRiot.js部分をReactを使って書いた。

Riot.js Redux Railsで作ったTodoアプリと全く同じものを作成するのでコードが大分重なっている(Riot.js部分をReactにしただけ)が、今回の一連の記事だけ読んでも出来るように省略はしない。

環境構築

Todo Appの作成

最終的に完成したTodo App
https://github.com/atfeo/React_Redux_Rails

参考

今回はRiot公式のTodoのようにチェックしてあるタスクを全て削除するボタンを作成する。

Rails

routes

まず複数削除のためのルートを追加

# ./config/routes.rb
namespace :api, format: 'json' do
  resources :tasks do
    delete :del_tasks, on: :collection
  end
end

controller

一度に複数削除する方法がわからないのでidを配列で受け取り一つずつ処理するようにした。

# ./app/controllers/api/tasks_controller.rb
def del_tasks
  ids = params[:ids]
  ids.each do |id|
    if Task.find(id).destroy
      head :no_content
    else
      head :unprocessable_entity
    end
  end
end

private

def task_params
  # idsを追加
  params.permit(:name, :isComplete, :ids)
end

サーバー側はこれで終了。

Del Tasksボタン

todo-app.jsxにDel Tasksのbuttonコンポーネントをマウントさせ、処理のためのメソッドも追加。

// ./client/src/components/todo-app.jsx
class TodoApp extends React.Component {
  constructor(props) {
    super(props);
    this.handleNewTask = this.handleNewTask.bind(this);
    this.handleInputForm = this.handleInputForm.bind(this);
    this.handleTaskCompletionChange = this.handleTaskCompletionChange.bind(this);
    // 追加
    this.handleDeleteTasks = this.handleDeleteTasks.bind(this);
  }

  complete(task) {
    return task.isComplete == true;
  }

  handleDeleteTasks() {
    let ids = [];
    const comp = this.props.tasks.filter((task) => this.complete(task));
    for (let i = 0; i < comp.length; i += 1) {
      ids[i] = comp[i].id;
    }
    this.props.dispatch(actions.deleteTasks(ids));
  }


    <button
      className="del"
      onClick={this.handleDeleteTasks}
      disabled={this.props.tasks.filter(this.complete).length == 0}
    >
      Del Tasks X {this.props.tasks.filter(this.complete).length}
    </button>
    <div style={{ clear: "both" }} />
    {this.props.isError ...

Del Tasksボタンをformの隣に配置するためにtask-formコンポーネントにstyleを加える。

// ./client/src/components/task-form.jsx
render() {
    return (
      <form
        onSubmit={this.handleSubmit}
        // 追加
        style={{ float: "left" }}
      >

先程決めたサーバーのURLに配列idsを送るメソッドなどをactions.jsに作成。

// ./client/src/actions.js
module.exports = {
  loadTasks,
  addTask,
  textExists,
  toggleComplete,
  // 追加
  deleteTasks,
};

function deleteTasks(ids) {
  return (dispatch) => {
    $.ajax({
      url: '/api/tasks/del_tasks',
      type: 'DELETE',
      dataType: 'json',
      data: { ids },
      success: () => {
        dispatch(deletedTasks(ids));
      },
      error: (xhr, status, err) => {
        dispatch(tempErrorMessage('API Error'));
        dispatch(toggleLoading(false));
        console.log('/api/tasks/del_tasks', status, err.toString());
      },
    });
  };
}

function deletedTasks(ids) {
  return { type: 'DELETED_TASKS', data: ids };
}

アクションが渡された時の処理をindex.jsxのreducerに追加。

// ./client/src/index.jsx
case 'DELETED_TASKS': {
  let newTasks = state.tasks.slice();
  for (let i = 0; i < action.data.length; i += 1) {
    const taskIndex = newTasks.findIndex((task) => {
      return task.id == action.data[i];
    });
    newTasks.splice(taskIndex, 1);
  }
  return Object.assign({}, state, { tasks: newTasks });
}

cssに以下を追加しDel Tasksボタンの色を変える。

/* ./app/assets/stylesheets/application.css */
.del {
   background-color: #d21073;
   border: 1px solid rgba(0,0,0,.2);
}

ブラウザでlocalhost:5000を更新する。チェックの数によってDel Tasksボタンの数値が変わること、ボタンを押して削除出来ることを確認。

ここまででほぼ完成。ついでに一つずつ削除する処理も追加しておく。

Rails

Controller

# ./app/controllers/api/tasks_controller.rb
def destroy
  if Task.find(params[:id]).destroy
    head :no_content
  else
    head :unprocessable_entity
  end
end

Delボタンの追加

まずDelボタンをtask-list.jsxに追加。

//  ./client/src/components/task-list.jsx
export default class TaskList extends React.Component {
  constructor(props) {
    super(props);
    this.handleCheck = this.handleCheck.bind(this);
    // 追加
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(e) {
    this.props.handledeletetask(e.target.id);
  }


    </label>
    // 追加
    {task.isComplete ? <button
      className="del"
      id={task.id}
      onClick={this.handleClick}
      style={{ float: "right" }}
    >
      Del
    </button> : null}

todo-app.jsxにもろもろ追加。

// ./client/src/components/todo-app.jsx
this.handleDeleteTasks = this.handleDeleteTasks.bind(this);
// 追加
this.handleDeleteTask = this.handleDeleteTask.bind(this);
}

handleDeleteTask(id) {
  this.props.dispatch(actions.deleteTask(id));
}


  <TaskList
    tasks={this.props.tasks}
    handlecheck={this.handleTaskCompletionChange}
    // 追加
    handledeletetask={this.handleDeleteTask}
  />

サーバーと通信するメソッドなどをactions.jsに追加。

// ./client/src/actions.js
module.exports = {
  loadTasks,
  addTask,
  textExists,
  toggleComplete,
  deleteTasks,
  // 追加
  deleteTask,
};

function deleteTask(id) {
  return (dispatch) => {
    dispatch(toggleLoading(true));
    $.ajax({
      url: `/api/tasks/${id}`,
      type: 'DELETE',
      dataType: 'json',
      success: () => {
        dispatch(deletedTask(id));
        dispatch(toggleLoading(false));
      },
      error: (xhr, status, err) => {
        dispatch(tempErrorMessage('API Error'));
        dispatch(toggleLoading(false));
        console.log('/api/tasks/del_tasks', status, err.toString());
      },
    });
  };
}

function deletedTask(id) {
  return { type: 'DELETED_TASK', data: id };
}

アクションが渡された時の処理をindex.jsxのreducerに追加。

// ./client/src/index.jsx
case 'DELETED_TASK': {
  const newTasks = state.tasks.filter((task) => {
    return task.id != action.data;
  });
  return Object.assign({}, state, { tasks: newTasks });
}
default:

ブラウザでlocalhost:5000を更新する。チェックしてあるタスクの右にDelボタンが表示されること、Delボタンを押して削除出来ることを確認。

React版Todoアプリの完成。