In the Loop with React: Crafting Your First Basic Todo List

ยท

6 min read

In this tutorial, we'll walk through the process of creating a basic Todo List application using React. The goal is to provide a beginner-friendly guide, breaking down each part of the code to help you understand how the application works.

Prerequisites

Before diving into the code, make sure you have a basic understanding of React. If you're new to React, consider going through the official React documentation and setting up a React development environment.

Setting Up Your React App

For this tutorial, we'll use Vite, a fast React development environment. If you haven't installed Vite, you can do so by running the following commands:

npm create vite@latest

External Dependencies

In addition to React, we'll use React Icons for the edit and delete icons in our Todo List. Install it using the following command:

npm i react-icons

Creating the Todo Component

import { useState } from "react";
import "./Todo.css";

import { FaRegEdit } from "react-icons/fa";
import { MdDelete } from "react-icons/md";

const Todo = () => {
  const initialData = {
    title: "",
    date: "",
  };

  const [formData, setFormData] = useState(initialData);
  const [todos, setTodos] = useState([]);
  const [editMode, setEditMode] = useState(false);
  const [editTodoId, setEditTodoId] = useState(null);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
  };

  const handleEdit = (id) => {
    setEditMode(true);

    const foundTodo = findTodoById(id);

    if (foundTodo) {
      setFormData(foundTodo);
      setEditTodoId(foundTodo.id);
    } else {
      alert("Todo not found");
      resetEditForm();
    }
  };

  const handleSaveEdit = () => {
    const updatedTodos = todos.map((todo) =>
      todo.id === editTodoId ? formData : todo
    );
    setTodos(updatedTodos);
    exitEditMode();
    setFormData(initialData);
  };

  const handleCancelEdit = () => {
    exitEditMode();
    resetEditForm();
  };

  const handleDelete = (id) => {
    const newTodos = todos.filter((todo) => todo.id !== id);
    setTodos(newTodos);
  };

  const handleSubmit = (e) => {
    if (editMode) {
      handleSaveEdit();
      return;
    }

    if (formData.title !== "" && formData.date !== "") {
      e.preventDefault();
      const newTask = createNewTask();
      setTodos([...todos, newTask]);
      resetEditForm();
    } else {
      alert("Please enter title and date");
    }
  };

  const exitEditMode = () => {
    setEditMode(false);
    setEditTodoId(null);
  };

  const resetEditForm = () => {
    setFormData(initialData);
    exitEditMode();
  };

  const findTodoById = (id) => todos.find((todo) => todo.id === id);

  const createNewTask = () => ({
    id: Math.floor(Math.random() * (100 - 1) + 1),
    title: formData.title,
    date: formData.date,
  });

  return (
    <div className="todoMainDiv">
      <h1 className="todoHeading">Todo List</h1>

      <div className="todo">
        <input
          name="title"
          type="text"
          placeholder="Title"
          value={formData.title}
          onChange={handleChange}
        />
        <input
          name="date"
          type="date"
          placeholder="Date"
          value={formData.date}
          onChange={handleChange}
        />
        <button onClick={handleSubmit}>{editMode ? "Update" : "Save"}</button>
        {editMode && <button onClick={handleCancelEdit}>Cancel</button>}{" "}
      </div>

      <div className="todoStatsTable">
        <table>
          <thead>
            <tr>
              <th>Title</th>
              <th>Date</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {todos.map((todo, index) => (
              <tr key={index}>
                <td>{todo.title}</td>
                <td>{todo.date}</td>
                <td className="actions">
                  <FaRegEdit
                    className="actionBtn"
                    onClick={() => handleEdit(todo.id)}
                  />
                  <MdDelete
                    className="actionBtn"
                    onClick={() => handleDelete(todo.id)}
                  />
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
};

export default Todo;

Import Necessary Dependencies and Styles

import { useState } from "react";
import "./Todo.css";

import { FaRegEdit } from "react-icons/fa";
import { MdDelete } from "react-icons/md";

FaRegEdit and MdDelete: These are components from the react-icons library, providing icons for the edit and delete actions.

State Variables using the useState Hook

const initialData = { 
    title: "", 
    date: "", 
};

const [formData, setFormData] = useState(initialData);
const [todos, setTodos] = useState([]);
const [editMode, setEditMode] = useState(false);
const [editTodoId, setEditTodoId] = useState(null);

This is a React hook used for managing state in functional components. Here, we're using it to create state variables for the form data, the list of todos, edit mode, and the ID of the todo being edited.

Event Handlers

const handleChange = (e) => {
  const { name, value } = e.target;
  setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
};

const handleEdit = (id) => {
  setEditMode(true);

  const foundTodo = findTodoById(id);

  if (foundTodo) {
    setFormData(foundTodo);
    setEditTodoId(foundTodo.id);
  } else {
    alert("Todo not found");
    resetEditForm();
  }
};

const handleSaveEdit = () => {
  const updatedTodos = todos.map((todo) =>
    todo.id === editTodoId ? formData : todo
  );
  setTodos(updatedTodos);
  exitEditMode();
  setFormData(initialData);
};

const handleCancelEdit = () => {
  exitEditMode();
  resetEditForm();
};

const handleDelete = (id) => {
  const newTodos = todos.filter((todo) => todo.id !== id);
  setTodos(newTodos);
};

const handleSubmit = (e) => {
  if (editMode) {
    handleSaveEdit();
    return;
  }

  if (formData.title !== "" && formData.date !== "") {
    e.preventDefault();
    const newTask = createNewTask();
    setTodos([...todos, newTask]);
    resetEditForm();
  } else {
    alert("Please enter title and date");
  }
};
  • handleChange: Updates the form data as the user types into the input fields.

  • handleEdit: Enters edit mode, retrieves the todo details, and sets them in the form for editing.

  • handleSaveEdit: Saves the edited todo, exits edit mode, and resets the form data.

  • handleCancelEdit: Cancels the edit action, exits edit mode, and resets the form data.

  • handleDelete: Deletes a todo by filtering it out from the list.

  • handleSubmit: Submits the form, either creating a new todo or updating an existing one.

Helper Functions

const exitEditMode = () => {
  setEditMode(false);
  setEditTodoId(null);
};

const resetEditForm = () => {
  setFormData(initialData);
  exitEditMode();
};

const findTodoById = (id) => todos.find((todo) => todo.id === id);

const createNewTask = () => ({
  id: Math.floor(Math.random() * (100 - 1) + 1),
  title: formData.title,
  date: formData.date,
});
  • exitEditMode: Exits edit mode and resets the edit todo ID.

  • resetEditForm: Resets the edit form by setting form data to initial values and exiting edit mode.

  • findTodoById: Finds a todo in the list by its ID.

  • createNewTask: Generates a new task with a random ID using form data.

JSX Structure

return (
  <div className="todoMainDiv">
    <h1 className="todoHeading">Todo List</h1>
    <div className="todo">
      <input
        name="title"
        type="text"
        placeholder="Title"
        value={formData.title}
        onChange={handleChange}
      />
      <input
        name="date"
        type="date"
        placeholder="Date"
        value={formData.date}
        onChange={handleChange}
      />

      <button onClick={handleSubmit}>{editMode ? "Update" : "Save"}</button>
      {editMode && <button onClick={handleCancelEdit}>Cancel</button>}{" "}
    </div>

    <div className="todoStatsTable">
      <table>
        <thead>
          <tr>
            <th>Title</th>
            <th>Date</th>
            <th>Actions</th>
          </tr>
        </thead>
         <tbody>
          {todos.map((todo, index) => (
            <tr key={index}>
              <td>{todo.title}</td>
              <td>{todo.date}</td>
              <td className="actions">
                <FaRegEdit
                  className="actionBtn"
                  onClick={() => handleEdit(todo.id)}
                />
                <MdDelete
                  className="actionBtn"
                  onClick={() => handleDelete(todo.id)}
                />
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  </div>
);
  • The JSX structure represents the UI of the Todo component.

  • Input fields allow the user to enter a title and date.

  • Buttons trigger actions such as submitting the form, canceling the edit action, and performing edit/delete actions.

  • The table displays the list of todos, with buttons for edit and delete actions.

Importing Todo Component and Running the App

In this section, we'll integrate the Todo component into the App.jsx file and run the React application to see the Todo List in action.

import Todo from "./components/Todo";

const App = () => {
  return <Todo />;
};

export default App;
npm run dev

Final Output

Explore the complete source code for a basic Todo List application developed with React.

Github Repo

I hope you're enjoying your journey into React development. ๐Ÿš€ If you have any questions or need assistance with the code, feel free to reach out. Don't hesitate to explore and customize the Todo List app to match your preferences. Coding is a creative process, so have fun experimenting with different features and styles!

Remember, every line of code you write is a step forward in your learning journey. Happy coding! ๐Ÿ’ป๐ŸŽ‰

ย