Search
⌘K
    to navigateEnterto select Escto close

    Deleting task

    In the last chapter, we saw how to update a task. In this chapter we'll see how to delete a task.

    Features

    These are the basic requirements of the feature:

    • A delete button should be present for all tasks.

    • Upon clicking the delete button, a DELETE HTTP request should be sent to the backend.

    • Once a response is received for the DELETE request a notification should be displayed with a success message or error message depending upon the response status.

    • User should be redirected to the dashboard page from the task deletion page if task is successfully deleted.

    Technical design

    To implement this feature, we need to introduce the following changes:

    On the backend

    • Add a destroy route for tasks RESTful resources in the routes file.

    • Add a destroy action in the TasksController. When application receives a DELETE request to delete a task, it will be processed by the delete action.

    • Update the before_action callback in the TasksController to call the load_task method before destroy action is called.

    On the frontend

    • Add a DELETE request API connector for deleting a task.

    • Update the Dashboard component and add a deleteTask function which will handle the logic to delete the task.

    • Pass deleteTask function as a prop to the TableRow component through Table component.

    • Update the TableRow component and add a delete button which will call the deleteTask function received through props.

    • Add a route in App component for rendering the EditTask component.

    Implementing destroy action in TasksController

    Let's implement the destroy action in our TasksController.

    Add the following lines of code into TasksController:

    1class TasksController < ApplicationController
    2  before_action :load_task, only: %i[show update destroy]
    3
    4  # previous code if any
    5
    6  def destroy
    7    if @task.destroy
    8      render status: :ok, json: { notice: 'Successfully deleted task.' }
    9    else
    10      render status: :unprocessable_entity,
    11        json: { error: @task.errors.full_messages.to_sentence }
    12    end
    13  end
    14
    15  private
    16
    17    # previous code if any

    Here, load_task methods runs before the destroy action and will have fetched the task whose slug matches our request parameter's slug value.

    If the task deletion is successful, then the status ok will be returned. On failure we will return status unprocessable_entity.

    Now, update the tasks resources in config/routes.rb file:

    1resources :tasks, except: %i[new edit], param: :slug

    Handling task deletion

    When user clicks on the "Delete" button then we need to handle that click. Then send a request to the server to delete the task. Let's handle it.

    First, let's add an API for deleting a task. To do so, add the following lines to app/javascript/src/apis/tasks.js:

    1import axios from "axios";
    2
    3const list = () => axios.get("/tasks");
    4
    5const show = slug => axios.get(`/tasks/${slug}`);
    6
    7const create = payload => axios.post("/tasks/", payload);
    8
    9const update = ({ slug, payload }) => axios.put(`/tasks/${slug}`, payload);
    10
    11const destroy = slug => axios.delete(`/tasks/${slug}`);
    12
    13const tasksApi = {
    14  list,
    15  show,
    16  create,
    17  update,
    18  destroy
    19};
    20
    21export default tasksApi;

    Open app/javascript/src/components/Dashboard/index.jsx and fully replace the content of the file with the code shown below.

    1import React, { useState, useEffect } from "react";
    2import { isNil, isEmpty, either } from "ramda";
    3
    4import Container from "components/Container";
    5import Table from "components/Tasks/Table";
    6import tasksApi from "apis/tasks";
    7import PageLoader from "components/PageLoader";
    8
    9const Dashboard = ({ history }) => {
    10  const [tasks, setTasks] = useState([]);
    11  const [loading, setLoading] = useState(true);
    12
    13  const fetchTasks = async () => {
    14    try {
    15      const response = await tasksApi.list();
    16      setTasks(response.data.tasks);
    17      setLoading(false);
    18    } catch (error) {
    19      logger.error(error);
    20      setLoading(false);
    21    }
    22  };
    23
    24  const destroyTask = async slug => {
    25    try {
    26      await tasksApi.destroy(slug);
    27      await fetchTasks();
    28    } catch (error) {
    29      logger.error(error);
    30    }
    31  };
    32
    33  const showTask = slug => {
    34    history.push(`/tasks/${slug}/show`);
    35  };
    36
    37  useEffect(() => {
    38    fetchTasks();
    39  }, []);
    40
    41  if (loading) {
    42    return (
    43      <div className="w-screen h-screen">
    44        <PageLoader />
    45      </div>
    46    );
    47  }
    48
    49  if (either(isNil, isEmpty)(tasks)) {
    50    return (
    51      <Container>
    52        <h1 className="text-xl leading-5 text-center">
    53          You have no tasks assigned 😔
    54        </h1>
    55      </Container>
    56    );
    57  }
    58
    59  return (
    60    <Container>
    61      <Table data={tasks} destroyTask={destroyTask} showTask={showTask} />
    62    </Container>
    63  );
    64};
    65
    66export default Dashboard;

    Now, we need to pass down destroyTask function as props to TableRow component. To do so, update Table.jsx with the following lines of code:

    1import React from "react";
    2import TableHeader from "./TableHeader";
    3import TableRow from "./TableRow";
    4
    5const Table = ({ data, showTask, destroyTask }) => {
    6  return (
    7    <div className="flex flex-col">
    8      <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
    9        <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
    10          <div className="overflow-hidden border-b border-bb-gray-200 shadow sm:rounded-lg">
    11            <table className="min-w-full divide-y divide-gray-200">
    12              <TableHeader />
    13              <TableRow
    14                data={data}
    15                showTask={showTask}
    16                destroyTask={destroyTask}
    17              />
    18            </table>
    19          </div>
    20        </div>
    21      </div>
    22    </div>
    23  );
    24};
    25
    26export default Table;

    Now, we need to make use of the destroyTask function so that a click on the Delete button in the tasks table would delete the corresponding task.

    To do so, go to app/javascript/src/components/Tasks/Table/TableRow.jsx and add the following lines:

    1import React from "react";
    2import PropTypes from "prop-types";
    3
    4const TableRow = ({ data, destroyTask, showTask }) => {
    5  return (
    6    <tbody className="bg-white divide-y divide-gray-200">
    7      {data.map(rowData => (
    8        <tr key={rowData.id}>
    9          <td
    10            className="block w-64 px-6 py-4 text-sm font-medium
    11            leading-8 text-bb-purple capitalize truncate"
    12          >
    13            {rowData.title}
    14          </td>
    15          <td className="px-6 py-4 text-sm font-medium leading-5 text-right cursor-pointer">
    16            <a
    17              className="text-bb-purple"
    18              onClick={() => showTask(rowData.slug)}
    19            >
    20              Show
    21            </a>
    22          </td>
    23          <td
    24            className="px-6 py-4 text-sm font-medium
    25            leading-5 text-right cursor-pointer"
    26          >
    27            <a
    28              className="text-red-500
    29              hover:text-red-700"
    30              onClick={() => destroyTask(rowData.slug)}
    31            >
    32              Delete
    33            </a>
    34          </td>
    35        </tr>
    36      ))}
    37    </tbody>
    38  );
    39};
    40
    41TableRow.propTypes = {
    42  data: PropTypes.array.isRequired,
    43  destroyTask: PropTypes.func,
    44  showTask: PropTypes.func
    45};
    46
    47export default TableRow;

    Deleting a task

    So let's go through the Destroy flow of our task.

    When we click the delete button, the onClick function uses the destroyTask() for making the API call and handles the task deletion provided the correct route and slug of the task.

    The control then goes to the router and router directs the control to destroy action of the TasksController.

    Once it's done, we again fetch the list of tasks from the DB, because the database should be the source of truth for the data we show in UI in all cases.

    1git add -A
    2git commit -m "Added ability to delete a task"
    Previous
    Next