Displaying list of tasks

Search icon
Search Book

In this chapter we will send the list of tasks in JSON format from the backend so that the frontend could display all those tasks.


These are the basic requirements of this feature:

  • A dashboard which is also the home page of our application.

  • An unordered list of all tasks which will be displayed on the application home page.

displaying list of tasks feature

Technical design

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

On the backend

  • Add an index action in the TasksController which will respond with a JSON of all tasks.

On the frontend

  • Add an API for fetching the list of tasks from the backend.

  • Dashboard component will call the API to fetch the tasks list and pass this list as a prop to the reusable Table component which we had already created.

  • Add a route in the App component to render the Dashboard.

Preparing to display list of tasks

First, let's create a file that contains the APIs for listing tasks. To do so, run the following command:

1touch app/javascript/src/apis/tasks.js

In tasks.js, paste the following content.

1import axios from "axios";
3const list = () => axios.get("/tasks");
5const tasksApi = { list };
7export default tasksApi;

Displaying list of tasks

Now let's try to display list of tasks from the database. We will create a Dashboard component which will render the Table component. To do so, run the following command:

1mkdir -p app/javascript/src/components/Dashboard/
2touch app/javascript/src/components/Dashboard/index.jsx

Inside index.jsx, paste the following contents:

1import React, { useState, useEffect } from "react";
2import { isNil, isEmpty, either } from "ramda";
4import Container from "components/Container";
5import Table from "components/Tasks/Table";
6import PageLoader from "components/PageLoader";
7import tasksApi from "apis/tasks";
9const Dashboard = () => {
10  const [tasks, setTasks] = useState([]);
11  const [loading, setLoading] = useState(true);
13  const fetchTasks = async () => {
14    try {
15      const {
16        data: { tasks },
17      } = await tasksApi.list();
18      setTasks(tasks);
19      setLoading(false);
20    } catch (error) {
21      logger.error(error);
22      setLoading(false);
23    }
24  };
26  useEffect(() => {
27    fetchTasks();
28  }, []);
30  if (loading) {
31    return (
32      <div className="w-screen h-screen">
33        <PageLoader />
34      </div>
35    );
36  }
38  if (either(isNil, isEmpty)(tasks)) {
39    return (
40      <Container>
41        <h1 className="text-xl leading-5 text-center">
42          You have no tasks assigned ๐Ÿ˜”
43        </h1>
44      </Container>
45    );
46  }
48  return (
49    <Container>
50      <Table data={tasks} />
51    </Container>
52  );
55export default Dashboard;

Now, we have created a Dashboard which will call the fetchTasks API and store the list of tasks fetched from the database in its state. It will pass down the lists of tasks to the Table component which will render a list of all tasks from our database.

Finally, let's create a React route to render our Dashboard component. Inside our App component we will also import and add the reusable PageLoader component to be displayed in place of the hardcoded text when loading state is true.

To do so, add the following lines to App.jsx:

1// previous imports
2import Dashboard from "components/Dashboard";
3import PageLoader from "components/PageLoader";
5const App = () => {
6  // previous code without any changes
8  if (loading) {
9    return (
10      <div className="h-screen">
11        <PageLoader />
12      </div>
13    );
14  }
16  return (
17    <Router>
18      <Switch>
19        // <--- rest of the code if any ----->
20        <Route exact path="/dashboard" component={Dashboard} />
21      </Switch>
22    </Router>
23  );
26export default App;

Importance of a PageLoader in the App Component

React mounts child components before parent components. In such a case child components like Dashboard might try to invoke their useEffect hook upon mounting which will in turn call the fetchTasks function in this case.

For fetching tasks we'd have to invoke the Axios APIs. The issue here is that in our backend we verify each API request and ensure that it has a valid auth token and email. These tokens are set in the headers of the request.

In order for those headers to be set correctly for all Axios requests, we invoke the setAuthHeaders function in the App component itself since it's the entry point to all other components. Thus we have to ensure that all child components are mounted only once Axios headers are successfully set.

In order to ensure that, we pass the setLoading function as an argument to setAuthHeaders. Initially, the loading state is set to true in the App component. Only once the setAuthHeaders function's execution is completed and auth headers are set for API requests, we set the loading state to false.

Ultimately in the App component we have to show a PageLoader component when the loading state is true. That's exactly what we did in the previous section.

Next let's update our NavBar component so that on clicking the Todos navitem, we get redirected to /dashboard.

Update the NavItem with name="Todos" in app/javascript/src/components/NavBar/index.jsx, to the following content:

1// imports as it was
2const NavBar = () => {
3  return (
4    // previous jsx as it was
5        <NavItem name="Todos" path="/dashboard" />
6        <NavItem
7          name="Add"
8          iconClass="ri-add-fill"
9          path="/tasks/create"
10        />
11    // previous jsx as it was
12  )
14// previous code as it was

Update the index action of TasksController, to send JSON response with the tasks:

1def index
2  tasks = Task.all
3  render status: :ok, json: { tasks: tasks }

Now visit URL http://localhost:3000/dashboard so that you can see all the tasks in the browser.

Now let's commit these changes:

1git add -A
2git commit -m "Added ability to display list of tasks"