Implementing Update Task

Now that we have everything in the right place we are all set to handle the task of building api to update a task.

We will start by adding the update action in TasksController. Let's open /app/controllers/tasks_controller.rb and change the code as shown below.

1class TasksController < ApplicationController
2  before_action :load_task, only: %i[show update]
3
4  ...
5
6  def update
7    if @task.update(task_params)
8      render status: :ok, json: { notice: 'Successfully updated task.' }
9    else
10      render status: :unprocessable_entity, json: { errors: @task.errors.full_messages }
11    end
12  end
13
14  private
15
16    ...

We have used the update method to save the updated values.

If the update is successful, we will return status ok with a notice. And on failure, we will return status unprocessable_entity along with the error messages.

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

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

Let's now create a new component for updating task details. To do so, like before, we will abstract the API logic and form logic to different components. First, let's add an API route to edit tasks inside app/javascript/src/apis/tasks.js

To do so, add the following lines to 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 tasksApi = {
12  list,
13  show,
14  create,
15  update,
16};
17
18export default tasksApi;

Now, let's create our react components to update task details. To do so, first run the following command:

1touch app/javascript/src/components/Tasks/EditTask.jsx

Inside EditTask.jsx, add the following content:

1import React, { useState, useEffect } from "react";
2import { useParams } from "react-router-dom";
3
4import Container from "components/Container";
5import TaskForm from "./Form/TaskForm";
6import tasksApi from "apis/tasks";
7import PageLoader from "components/PageLoader";
8import Toastr from "components/Common/Toastr";
9
10const EditTask = ({ history }) => {
11  const [title, setTitle] = useState("");
12  const [userId, setUserId] = useState("");
13  const [loading, setLoading] = useState(false);
14  const [pageLoading, setPageLoading] = useState(true);
15  const { slug } = useParams();
16
17  const handleSubmit = async event => {
18    event.preventDefault();
19    try {
20      await tasksApi.update({ slug, payload: { task: { title, user_id: userId } } });
21      setLoading(false);
22      Toastr.success("Successfully updated task.")
23      history.push("/dashboard");
24    } catch (error) {
25      setLoading(false);
26      logger.error(error);
27    }
28  };
29
30  const fetchTaskDetails = async () => {
31    try {
32      const response = await tasksApi.show(slug);
33      setTitle(response.data.task.title);
34      setUserId(response.data.task.user_id);
35    } catch (error) {
36      logger.error(error);
37    } finally {
38      setPageLoading(false);
39    }
40  };
41
42  useEffect(() => {
43    fetchTaskDetails();
44  }, []);
45
46  if (pageLoading) {
47    return (
48      <div className="w-screen h-screen">
49        <PageLoader />
50      </div>
51    );
52  }
53
54  return (
55    <Container>
56      <TaskForm type="update" title={title} userId={userId} setTitle={setTitle} setUserId={setUserId} loading={loading} handleSubmit={handleSubmit} />
57    </Container>
58  );
59};
60
61export default EditTask;

TaskForm is the reusable Form UI that we had created while working on creating a task. Here, fetchTaskDetails function is used to pre-populate the input field with the existing title of the task.

Now, we need to create a route inside of our App.jsx. To do so, open App.jsx and add the following lines:

1import React, { useEffect, useState } from "react";
2import CreateTask from "components/Tasks/CreateTask";
3import EditTask from "components/Tasks/EditTask";
4import Dashboard from "components/Dashboard";
5
6import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
7const App = () => {
8  return (
9    <Router>
10      <Switch>
11        <Route exact path="/tasks/:slug/edit" component={EditTask} />
12        <Route exact path="/tasks/create" component={CreateTask} />
13        <Route exact path="/dashboard" component={Dashboard} />
14      </Switch>
15    </Router>
16  );
17};
18
19export default App;
20

Once we have added a route in react router dom to render our component, we need to use the update API in our previously created reusable Table component.

1import React from "react";
2import PropTypes from "prop-types";
3
4const TableRow = ({ data, destroyTask, updateTask }) => {
5  return (
6    <tbody className="bg-white divide-y divide-gray-200">
7      {
8        data.map(rowData => (
9          <tr key={rowData.title}>
10            <td className="px-6 py-4 text-sm font-medium leading-5 text-gray-900 whitespace-no-wrap">
11                {rowData.title}
12            </td>
13            <td className="px-6 py-4 text-sm font-medium leading-5 text-gray-900 whitespace-no-wrap">
14                {rowData.user_id}
15            </td>
16            <td className="px-6 py-4 text-sm font-medium leading-5 text-right cursor-pointer">
17                <a className="text-indigo-600 hover:text-indigo-900" onClick={() => updateTask(rowData.id)}>Edit</a>
18            </td>
19            <td className="px-6 py-4 text-sm font-medium leading-5 text-right cursor-pointer">
20                <a className="text-red-500 hover:text-red-700">Delete</a>
21            </td>
22          </tr>
23        ))
24      }
25    </tbody>
26  );
27};
28
29TableRow.propTypes = {
30    data: PropTypes.array.isRequired,
31    destroyTask: PropTypes.func,
32    updateTask: PropTypes.func
33};
34
35export default TableRow;

Finally, add the updateTask function to app/javascript/src/components/Dashboard/index.jsx. To do so, add the following lines:

1import React, { useState, useEffect } from "react";
2import { isNil, isEmpty, either } from "ramda";
3
4import Container from "components/Container";
5import ListTasks from "components/Tasks/ListTasks";
6import tasksAPI from "apis/tasks";
7import PageLoader from "components/PageLoader";
8
9const Dashboard = ({ history }) => {
10
11  // --- Previous Content ---
12  const updateTask = slug => {
13    history.push(`/tasks/${slug}/edit`);
14  };
15
16  if (either(isNil, isEmpty)(tasks)) {
17    return (
18      <Container>
19        <h1 className="text-xl leading-5 text-center">You have no tasks assigned ๐Ÿ˜”</h1>
20      </Container>
21    );
22  }
23
24  return (
25    <Container>
26      <ListTasks data={tasks} destroyTask={destroyTask} updateTask={updateTask} showTask={showTask} />
27    </Container>
28  );
29};
30
31export default Dashboard;
32

Now, on the dashboard page while listing tasks, clicking on Edit would render the EditTask component that we just created where we will be able to edit task details.

1git add -A
2git commit -m "Added ability to update a task"