React - Task component

The Task component will manage many things:

  • displaying a single task,

  • retrieving operations assigned to the task from the API,

  • hiding and showing the form for adding operations,

  • updating and deleting the task.

The following props set is given to the component:

  • title - task title,

  • description - task description,

  • id - task ID,

  • status - task status (open/closed), save it under the variable _status because of the local state with the same name,

  • onRemoveTask - a method from the parent that allows us to remove the task from the local state of the App.

function Task({title, description, id, status: _status, onRemoveTask}) {

}

Start by creating all the states needed for the component to work:

  1. status - current status of our component. The initial value will be the status received through props.

  2. operations - an array of operations for the specific task.

  3. operationForm - value true/false indicating whether the form to add the task should be opened or closed.

const [status, setStatus] = useState(_status);
const [operations, setOperations] = useState([]);
const [operationForm, setOperationForm] = useState(false);

Tasks

This component must include methods that handle updating (Finish) and deleting a task. The handleFinish method must prepare the object with the task (exactly as the NewTask component earlier). In this case, status will have the value of closed.

We use here the updateTask API method, which we created earlier. We send it the id of our task, task object, and also the function that will be called when the API query has been called. This method will change the local state of the task to closed.

/**
* Update task and set status to "finish"
*/
const handleFinish = () => {
  const task = {
    title,
    description,
    status: "closed"
  };

  /**
   * @function updateTask - API function
   */
  updateTask(id, task, () => {
    setStatus("closed");
  });
};

The function used for deleting tasks looks very similar. However, it calls another API method - removeTask. We pass it the id of the task, and the method that will be called when the task is actually removed from the database. In it, we use the method received from the parent - onRemoveTask and send the id. This will remove the task with the given ID from the local state of the App component and stop rendering it.

/**
* Remove single task from DB and local state
*/
const handleRemove = () => {
  /**
   * @function removeTask - API function
   */
  removeTask(id, () => {
    /**
     * @function onRemoveTask - Function from parent component passed by props
     * Function is updating local state
     */
    onRemoveTask(id);
  });
};

Operations

Next, let's create a function that will allow us to switch the operationForm state. We'll attach it to the Add operation button.

/**
* Show/Hide add new operation form
*/
const toggleOperationForm = () => {
  setOperationForm(prevState => !prevState);
};

Of course, we have to retrieve all operations from the API server. We remember that such operations are performed after mounting the component, so here we will use the useEffect hook. We provide it with a function in which we call the getOperations API method with a task ID and a method that saves the retrieved data to the local state. We also pass an empty dependency array so that the data is retrieved only once during the component's lifecycle.

useEffect(() => {
  /**
   * After component mount fetch all operation in this task
   * @function getOperations - API function
   */
  getOperations(id, setOperations);
}, []);

JSX structure

Add title and description, and make the displaying of buttons conditional.

The "Add operation" and "Finish" buttons should be visible only if the task has status: "open".

The button for deleting a task, on the other hand, can only appear if there are no more operations assigned to the task: operations.length === 0 (the server will not allow the deletion of a task with existing operations).

Finally, attach the Operations component which we will discuss in the next article. It receives all retrieved operations (props), the state of the form (open/closed) together with the method, task ID and its status.

return (
    <section className="card mt-5 shadow-sm">
      <div className="card-header d-flex justify-content-between align-items-center">
        <div>
          <h5>{title}</h5>
          <h6 className="card-subtitle text-muted">{description}</h6>
        </div>


        <div>
          {status === "open" && (
            <>
              <Button icon="fas fa-plus-circle"
                      color="info"
                      size="sm"
                      onClick={toggleOperationForm}
                      className="mr-2">
                Add operation
              </Button>

              <Button icon="fas fa-archive"
                      color="dark"
                      size="sm"
                      onClick={handleFinish}>
                Finish
              </Button>
            </>
          )}
          {operations.length === 0 &&
          <Button icon={"fas fa-trash"} color={"danger"} outline size={"sm"} onClick={handleRemove}
                  className="ml-2"/>}
        </div>
      </div>

      <Operations taskID={id}
                  form={operationForm}
                  setForm={setOperationForm}
                  operations={operations}
                  setOperations={setOperations}
                  status={status}/>
    </section>
);
import React, {useState, useEffect} from "react";
import Operations from "./Operations";
import Button from "./Button";
import {removeTask, updateTask} from "../api/tasks";
import {getOperations} from "../api/operations";

function Task({title, description, id, status: _status, onRemoveTask}) {
  const [status, setStatus] = useState(_status);
  const [operations, setOperations] = useState([]);
  const [operationForm, setOperationForm] = useState(false);

  /**
   * Update task and set status to "finish"
   */
  const handleFinish = () => {
    const task = {
      title,
      description,
      status: "closed"
    };

    /**
     * @function updateTask - API function
     */
    updateTask(id, task, () => {
      setStatus("closed");
    });
  };

  /**
   * Remove single task from DB and local state
   */
  const handleRemove = () => {
    /**
     * @function removeTask - API function
     */
    removeTask(id, () => {
      /**
       * @function onRemoveTask - Function from parent component passed by props
       * Function is updating local state
       */
      onRemoveTask(id);
    });
  };


  /**
   * Show/Hide add new operation form
   */
  const toggleOperationForm = () => {
    setOperationForm(prevState => !prevState);
  };

  useEffect(() => {
    /**
     * After component mount fetch all operation in this task
     * @function getOperations - API function
     */
    getOperations(id, setOperations);
  }, []);


  return (
    <section className="card mt-5 shadow-sm">
      <div className="card-header d-flex justify-content-between align-items-center">
        <div>
          <h5>{title}</h5>
          <h6 className="card-subtitle text-muted">{description}</h6>
        </div>


        <div>
          {status === "open" && (
            <>
              <Button icon="fas fa-plus-circle"
                      color="info"
                      size="sm"
                      onClick={toggleOperationForm}
                      className="mr-2">
                Add operation
              </Button>

              <Button icon="fas fa-archive"
                      color="dark"
                      size="sm"
                      onClick={handleFinish}>
                Finish
              </Button>
            </>
          )}
          {operations.length === 0 &&
          <Button icon={"fas fa-trash"} color={"danger"} outline size={"sm"} onClick={handleRemove}
                  className="ml-2"/>}
        </div>
      </div>

      <Operations taskID={id}
                  form={operationForm}
                  setForm={setOperationForm}
                  operations={operations}
                  setOperations={setOperations}
                  status={status}/>
    </section>
  );
}

export default Task;

Last updated