React on Firebase!๐Ÿ”ฅ: Part 3

Subrata ChSubrata Ch
5 min read

In the last part of the series (Part 2), we added a feature for adding tasks and input validation. In this part, we will update our App.jsx file to add two more functionalities: toggleTaskDone and sort. When the user clicks on a task to mark it as completed, it will apply some CSS highlight styling and move the task to the end of the list. If clicked again, the task will revert to its original state. To achieve this, we will only update our App.jsx file. The comments will help you understand the purpose of each code snippet.

App.jsx (updated)

import React, { useState } from "react"; // Importing React and the useState hook for managing state within the component.
import "./App.css"; // Importing custom styles if needed. This assumes you have an App.css file in the same directory.

const App = () => {
  const [tasks, setTasks] = useState([]); // State to store the list of tasks
  const [task, setTask] = useState(""); // State to store the current task input
  const [error, setError] = useState(""); // State to store the error message

  // Function to add anew task.
  const addTask = (task) => {
    setTasks([...tasks, task]);
    // Spreads the existing tasks into a new array, adding the new task at the end.
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // Prevents the default behavior of the form submission (which would cause a page reload).

    if (task.trim() === "") {
      setError("Task cannot be empty");
    } else {
      addTask({ id: Date.now(), text: task, done: false });
      // Adds a new task object with a unique ID, the task text, and a 'done' status of false.
      setTask("");
      setError("");
    }
  };

  const toggleTaskDone = (id) => {
    // Function to toggle the 'done' status of a task by its ID.
    setTasks(
      tasks.map(
        (task) => (task.id === id ? { ...task, done: !task.done } : task)
        // Iterates over each task in the tasks array.
        // If the task's ID matches the provided ID, it returns a new task object with the 'done' status toggled.
        // Otherwise, it returns the task unchanged.
      )
    );
  };

  const sortedTasks = [...tasks].sort((a, b) => a.done - b.done);
  // Creates a sorted copy of the tasks array.
  // Tasks are sorted such that incomplete tasks (done: false) appear before completed tasks (done: true).

  return (
    <div className="App container mt-4">
      <h1 className="text-center mb-4">To-Do List v2</h1>
      <h5 className="text-center text-muted">
        UPDATE TASK (toggleTaskDone Function)
      </h5>

      <form onSubmit={handleSubmit} className="mb-4">
        <div className="input-group">
          <input
            type="text"
            className={`form-control ${error ? "is-invalid" : ""}`}
            // The input field for entering a new task.
            // Adds the 'is-invalid' class if there's an error (to highlight the field with a red border).
            value={task}
            // Binds the input value to the 'task' state.
            onChange={(e) => setTask(e.target.value)}
            // Updates the 'task' state with the current input value when the user types.
            placeholder="Add a new task"
            // Placeholder text shown in the input field when it's empty.
          />

          <button type="submit" className="btn btn-primary">
            Add Task
            {/* The button to submit the form and add the new task. */}
          </button>
        </div>

        {error && <div className="invalid-feedback d-block alert alert-danger">{error}</div>}
        {/* Conditionally renders the error message below the input field if there's an error. 
            The 'd-block' class ensures the error message takes up space even if it's empty. */}
      </form>

      <ul className="list-group">
        {sortedTasks.map((task, index) => (
          // Iterates over each task in the sortedTasks array and returns a list item for each.
          <li
            key={task.id}
            // The 'key' prop ensures each list item has a unique identifier for efficient rendering.

            className={`list-group-item d-flex justify-content-between align-items-center ${
              task.done
                ? "list-group-item-secondary text-decoration-line-through text-danger"
                : ""
            }`}
            // Applies conditional classes:
            // 'list-group-item-secondary' and 'text-decoration-line-through' if the task is marked as done (for a strikethrough effect).
          >
            <span onClick={() => toggleTaskDone(task.id)} role="button">
              {/* The task text inside a span that can be clicked to toggle the task's done status. */}
              {index + 1}: {task.text}
              {/* Displays the task's position in the list and its text. */}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;
// Exports the App component as the default export, so it can be imported and used in other files.

I also have added an input validation. together, they look like as below ๐ŸŒฌ:

With the updated (completed) tasks โœ…โœ๏ธ: Upon clicking on any undone task, the app will move it to the bottom and mark it as done using CSS styling. Clicking on a completed task again will toggle it back to its initial state.

๐Ÿ— Key things to watch out for: ๐Ÿ‘€

โžก ThetoggleTaskDone() function: ๐Ÿšจ

  const toggleTaskDone = (id) => {
    // Function to toggle the 'done' status of a task by its ID.
    setTasks(
      tasks.map(
        (task) => (task.id === id ? { ...task, done: !task.done } : task)
        // Iterates over each task in the tasks array.
        // If the task's ID matches the provided ID, it returns a new task object with the 'done' status toggled.
        // Otherwise, it returns the task unchanged.
      )
    ); 
  };

โžก Use ofsort()method: โ™ป๏ธ

  const sortedTasks = [...tasks].sort((a, b) => a.done - b.done);
  // Creates a sorted copy of the tasks array.
  // Tasks are sorted such that incomplete tasks (done: false) appear before completed tasks (done: true).

โžก sortedTasks.map() method: use of a ternary operator for className swap: ๐Ÿ”€

<ul className="list-group">
  {sortedTasks.map((task, index) => (
    // Iterates over each task in the sortedTasks array and returns a list item for each.
    <li
      key={task.id}
      // The 'key' prop ensures each list item has a unique identifier for efficient rendering.

      className={`list-group-item d-flex justify-content-between align-items-center ${
        task.done
          ? "list-group-item-secondary text-decoration-line-through text-danger"
          : ""
      }`}
      // Applies conditional classes:
      // 'list-group-item-secondary' and 'text-decoration-line-through' if the task is marked as done (for a strikethrough effect).
    >
      <span onClick={() => toggleTaskDone(task.id)} role="button">
        {/* The task text inside a span that can be clicked to toggle the task's done status. */}
        {index + 1}: {task.text}
        {/* Displays the task's position in the list and its text. */}
      </span>
    </li>
  ))}
</ul>

In our next part (Part 4), we will add a deleteTask function to remove specific tasks from the list. Each task item will have a delete button and a confirmation prompt to prevent accidental deletions.

0
Subscribe to my newsletter

Read articles from Subrata Ch directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Subrata Ch
Subrata Ch

Software Engineer & Consultant