DEV Community

OGOSHI VALENTINE JOHN
OGOSHI VALENTINE JOHN

Posted on • Edited on

Building a Dynamic To-Do List App with React: A Comprehensive Guide

Are you looking for an efficient way to manage your tasks and improve your productivity? In this article, we will explore how to build a dynamic to-do list app using React, a popular JavaScript library for building user interfaces. We will walk through the code step by step, explaining its functionality and demonstrating how you can customize it to suit your needs.

Prerequisites

Before we begin, make sure you have the following installed:
Node.js (version 14 or higher)
npm (Node Package Manager)

STEP 1: Setting Up the Project

To start, create a new directory for your project and navigate to it in your terminal or command prompt. Run the following command to initialize a new React project:

npx create-react-app todo-list-app
cd todo-list-app

Enter fullscreen mode Exit fullscreen mode

STEP 2: Installing Dependencies

In this step, we need to install additional dependencies required for our Todo List app. Run the following command to install the required packages:

npm install react-toastify react-modal react-toastify

Enter fullscreen mode Exit fullscreen mode

STEP 3: Setting up the Component

Open the src directory in your project and create a new file called TodoList.js. This file will contain our main component.

In the TodoList.js file, import the necessary dependencies and assets:

import React, { useEffect, useState } from "react";
import { ToastContainer, toast } from 'react-toastify';
import notificationSound from './assets/Best-Reminder.mp3'
import notificationSound2 from './assets/OrchestralEmergencyAlarm.wav'
import 'react-toastify/dist/ReactToastify.css';
import Modal from 'react-modal';

Enter fullscreen mode Exit fullscreen mode

STEP 4: Defining the TodoList Component

Inside the TodoList.js file, define a functional component named TodoList using the arrow function syntax:

const TodoList = () => {
  // Component logic will go here
}

Enter fullscreen mode Exit fullscreen mode

STEP 5: Initializing State Variables

Within the TodoList component, define state variables using the useState hook. These variables will store the tasks, input value, start times, finish times, modal state, edit index, edited value, completed tasks, and snooze times:

const [tasks, setTasks] = useState(JSON.parse(localStorage.getItem('tasks')) || []);
const [inputValue, setInputValue] = useState('');
const [startTimes, setStartTimes] = useState({});
const [finishTimes, setFinishTimes] = useState({});
const [modalIsOpen, setModalIsOpen] = useState(false);
const [showHistoryModal, setShowHistoryModal] = useState(false);
const [editIndex, setEditIndex] = useState(-1);
const [editedValue, setEditedValue] = useState('');
const [completedTasks, setCompletedTasks] = useState(JSON.parse(localStorage.getItem('completedTasks')) || []);
const [snoozeTimes, setSnoozeTimes] = useState({});
const audio = new Audio(notificationSound);
const audio2 = new Audio(notificationSound2);

Enter fullscreen mode Exit fullscreen mode

In the above, we initialize the state variables with the values retrieved from localStorage or default values if no data is available.

STEP 6: Configuring the Modal

We will use the Modal component from react-modal to create a modal dialog for editing tasks. Add the following code to configure the modal:

const customStyles = {
  content: {
    top: '50%',
    left: '50%',
    right: 'auto',
    bottom: 'auto',
    marginRight: '-50%',
    transform: 'translate(-50%, -50%)',
    width: '50%',
  },
};

Modal.setAppElement('#root');

Enter fullscreen mode Exit fullscreen mode

In the above, we define custom styles for the modal and set the app element for accessibility purposes.

STEP 7: Setting up Local Storage Effects

To persist our tasks and completed tasks in local storage, we will use the useEffect hook. Add the following code to store the tasks and completed tasks whenever they change:

useEffect(() => {
  localStorage.setItem('tasks', JSON.stringify(tasks));
}, [tasks]);

useEffect(() => {
  localStorage.setItem('completedTasks', JSON.stringify(completedTasks));
}, [completedTasks]);

Enter fullscreen mode Exit fullscreen mode

The first useEffect will store the tasks in local storage whenever it changes, and the second useEffect will do the same for the completedTasks.

STEP 8: Handling Input Change

We need a handler function to update the input value as the user types. Add the following code to define the handleInputChange function:

const handleInputChange = e => {
  setInputValue(e.target.value);
};

Enter fullscreen mode Exit fullscreen mode

STEP 9: Handling Task Addition

To add a new task, we will define the handleAddTask function. This function will validate the input, add the task to the tasks array, and display a success or error toast message. Add the following code:

const handleAddTask = () => {
  if (inputValue.trim() !== '') {
    setTasks([...tasks, { id: Date.now(), text: inputValue, completed: false }]);
    setInputValue('');
    setStartTimes({ ...startTimes, [tasks.length]: null });
    setFinishTimes({ ...finishTimes, [tasks.length]: null });

    toast.success(`Task "${inputValue}" added successfully!`, {
      autoClose: 3000,
      position: toast.POSITION.TOP_CENTER,
    });
  } else {
    toast.error('Please enter a task', {
      autoClose: 3000,
      position: toast.POSITION.TOP_CENTER,
    });
  }
};

Enter fullscreen mode Exit fullscreen mode

STEP 10: Handling Task Deletion

To delete a task, we will define the handleDeleteTask function. This function will remove the task from the tasks array and associated start and finish times. It will also display a success toast message. Add the following code:

const handleDeleteTask = (index) => {
  setTasks(tasks.filter((task, i) => i !== index));
  const newStartTimes = { ...startTimes };
  const newFinishTimes = { ...finishTimes };
  delete newStartTimes[index];
  delete newFinishTimes[index];
  setStartTimes(newStartTimes);
  setFinishTimes(newFinishTimes);

  toast.success(`Your task has been deleted!`, {
    autoClose: 5000,
    position: toast.POSITION.TOP_CENTER,
  });
};

Enter fullscreen mode Exit fullscreen mode

STEP 11: Handling Task Editing

To edit a task, we will define the handleEditTask function. This function will set the edit index, pre-fill the modal with the task's current value, and open the modal. Add the following code:

const handleEditTask = (index) => {
  setEditIndex(index);
  setEditedValue(tasks[index].text);
  setModalIsOpen(true);
};

Enter fullscreen mode Exit fullscreen mode

STEP 12: Handling Task Completion

When a task is marked as completed, we will define the handleCompleteTask function. This function will update the task's completion status, display a success toast message, and move the completed task to the completedTasks array. Add the following code:

const handleCompleteTask = (index) => {
  const newTasks = [...tasks];
  newTasks[index].completed = true;
  setTasks(newTasks);

  toast.success(`Your task is completed!`, {
    autoClose: 5000,
    position: toast.POSITION.TOP_CENTER,
  });

  // Move the completed tasks to the history
  const completedTask = newTasks[index];
  setCompletedTasks([...completedTasks, completedTask]);
};

Enter fullscreen mode Exit fullscreen mode

STEP 13: Handling Modal Save and Close

For the modal functionality, we need to define the handleModalSave and handleModalClose functions. The handleModalSave function will update the edited task's value and close the modal, while the handleModalClose function will simply close the modal. Add the following code:

const handleModalSave = () => {
  const newTasks = [...tasks];
  newTasks[editIndex].text = editedValue;
  setTasks(newTasks);
  setModalIsOpen(false);

  toast.success(`Your task has been updated!`, {
    autoClose: 5000,
    position: toast.POSITION.TOP_CENTER,
  });
};

const handleModalClose = () => {
  setModalIsOpen(false);
};

Enter fullscreen mode Exit fullscreen mode

STEP 14: Handling Start and Finish Times

To set start and finish times for tasks, we will define the handleSetStartTime and handleSetFinishTime functions. These functions will update the start and finish times respectively and display success toast messages. Add the following code:

const handleSetStartTime = (index, startTime) => {
  setStartTimes({ ...startTimes, [index]: startTime });
  setSnoozeTimes({ ...snoozeTimes, [index]: startTime });

  toast.success(`Notification successfully set!`, {
    autoClose: 5000,
    position: toast.POSITION.TOP_CENTER,
  });
};

const handleSetFinishTime = (index, finishTime) => {
  setFinishTimes({ ...finishTimes, [index]: finishTime });

  toast.success(`Notification successfully set!`, {
    autoClose: 5000,
    position: toast.POSITION.TOP_CENTER,
  });
};

Enter fullscreen mode Exit fullscreen mode

STEP 15: Handling Completed Task Deletion

To delete a completed task, we will define the handleDeleteCompletedTask function. This function will remove the completed task from the completedTasks array. Add the following code:

const handleDeleteCompletedTask = (index) => {
  setCompletedTasks(completedTasks.filter((completedTask, i) => i !== index));
};

Enter fullscreen mode Exit fullscreen mode

STEP 16: Handling Snooze

To snooze a task, we will define the handleSnoozeTask function. This function will set a snooze time for the task and display an info toast message. Add the following code:

const handleSnoozeTask = (index, snoozeDuration) => {
  const now = new Date();
  const snoozeTime = new Date(now.getTime() + snoozeDuration * 60000); // Convert minutes to milliseconds

  setStartTimes({ ...startTimes, [index]: snoozeTime });
  setSnoozeTimes({ ...snoozeTimes, [index]: snoozeTime });

  toast.info(`Task "${tasks[index].text}" snoozed for ${snoozeDuration} minutes!`, {
    autoClose: 5000,
    position: toast.POSITION.TOP_CENTER,
  });
};

Enter fullscreen mode Exit fullscreen mode

Snooze Functionality

The handleSnoozeTask function is responsible for snoozing a task for a specified duration. When called, it receives the index of the task and the snoozeDuration in minutes. Here's how it works:

  1. Get the current date and time using new Date().
  2. Calculate the snooze time by adding the snooze duration (converted to milliseconds) to the current time.
  3. Update the startTimes and snoozeTimes state by merging the existing values with the new snooze time using the spread operator (...).
  4. Display a toast notification using a library like toast.info() to inform the user that the task has been snoozed. The notification includes the task's text and the snooze duration.

STEP 17: Handling Task Reminders and Alerts

To handle task reminders and alerts, we will use the useEffect hook. Add the following code to set up the interval:

 useEffect(() => {
        const intervalID = setInterval(() => {
            const now = new Date();
            tasks.forEach((task, index) => {
                if (startTimes[index] && now >= startTimes[index]) {
                    audio.play();

                    toast.success(`You have two minutes left to start your task: ${task.text}`, {
                        autoClose: 5000,
                        position: toast.POSITION.TOP_CENTER,
                    });

                    setStartTimes({ ...startTimes, [index]: null });
                }

                if (finishTimes[index] && now >= finishTimes[index]) {
                    audio2.play();
                    toast.warning(`You have 2 minutes left to finish your task: ${task.text}`, {
                        autoClose: 5000,
                        position: toast.POSITION.TOP_CENTER,
                    });

                    setFinishTimes({ ...finishTimes, [index]: null });
                }

                if (snoozeTimes[index] && now >= snoozeTimes[index]) {
                    audio.play();

                    toast.success(`Time to start your snoozed task: ${task.text}`, {
                        autoClose: 5000,
                        position: toast.POSITION.TOP_CENTER,
                    });

                    setStartTimes({ ...startTimes, [index]: null });
                    setSnoozeTimes({ ...snoozeTimes, [index]: null });
                }
            });
        }, 1000);

        return () => clearInterval(intervalID);
    }, [tasks, startTimes, finishTimes, snoozeTimes]);
Enter fullscreen mode Exit fullscreen mode

Task Notifications

The useEffect hook is used to manage task notifications. It sets up a recurring interval (every second) to check the task statuses and display notifications when necessary. Here's how it works:

When the component mounts or any of the dependencies (tasks, startTimes, finishTimes, or snoozeTimes) change, the effect is triggered.

Within the effect, an interval is created using setInterval() to run the notification logic repeatedly.

For each task, the following checks are performed:

  1. If the task's start time exists and the current time is greater than or equal to the start time, the notification is triggered using toast.success(). The audio is played, and the start time for that task is cleared.

  2. If the task's finish time exists and the current time is greater than or equal to the finish time, the notification is triggered using toast.warning(). The audio2 is played, and the finish time for that task is cleared.

  3. If the task's snooze time exists and the current time is greater than or equal to the snooze time, the notification is triggered using toast.success().

  4. The audio is played, and both the start time and snooze time for that task are cleared.

  5. Finally, the effect returns a cleanup function that clears the interval using clearInterval() when the component unmounts or when the dependencies change.

STEP 18: Rendering the Todo List

In the return statement, we render the JSX for our Todo List app. It consists of a title, an input field for adding tasks, a button to show the completed tasks modal, and a list of tasks. Each task is displayed as an item with buttons to delete, edit, mark as done, set start and finish times, and snooze. The modal dialog is used for editing tasks. The completed tasks are shown in a separate modal dialog.

At the end of the component, we render the ToastContainer component fromreact-toastifyto display toast notifications. Finally, we will render the todo list in theTodoList` component's JSX. Add the following code:

`
return (


My To-Do List App





Add Task
View Completed Tasks
            <ul className="action-buttons">
                {tasks.map((task, index) => (
                    <li key={index} style={{ textDecoration: task.completed ? 'Line-Through' : 'none' }}>
                        {task.text}
                        <button className="deleteTask" onClick={() => handleDeleteTask(index)} style={{ background: 'red' }}>X</button>
                        <button className="editTask" onClick={() => handleEditTask(index)} disabled={task.completed} style={{ background: 'blue' }} >Edit</button>

                        {!task.completed && (
                            <button onClick={() => handleCompleteTask(index)} style={{ background: 'green' }}>Done</button>
                        )}
                        <input type="datetime-local" className="datetime-local" onChange={(e) => handleSetStartTime(index, new Date(e.target.value))} disabled={task.completed} />
                        <input type="datetime-local" className="datetime-local" onChange={(e) => handleSetFinishTime(index, new Date(e.target.value))} disabled={task.completed} />
                        <button onClick={() => handleSnoozeTask(index, 1)} disabled={task.completed}>Snooze</button>
                    </li>
                ))}

                <Modal isOpen={showHistoryModal} onRequestClose={handleCloseHistoryModal} contentLabel="Completed Tasks" >
                    <div className="history-modal">
                        <h2 className="historyHeader">Completed Tasks</h2>

                        {completedTasks.length > 0 ? (

                        <ul>
                            {completedTasks.map((task, index) => (
                                <li key={index} className="completedTasks">
                                    <input className="checkBox" type="checkbox" checked="yes" style={{ background: 'green' }} />
                                    {task.text}
                                    <button style={{ backgroundColor: 'red'}} onClick={() => handleDeleteCompletedTask(index)}> X </button>
                                </li>
                            ))}
                        </ul>

                        ) : (
                        <p className="zeroDoneTasks" style={{color: 'black'}}>No completed tasks.</p>
                        )}

                        <div className="Close-Modal">
                            <button  onClick={handleCloseHistoryModal}>Close</button>
                        </div>

                    </div>
            </Modal>

                <Modal isOpen={modalIsOpen} onRequestClose={handleModalClose} style={customStyles}>
                    <h2 className="editTask">Edit task</h2>
                    <div className="inputHeader">
                        <input className="inputMod " type="text" value={editedValue} onChange={(e) => setEditedValue(e.target.value)} />
                    </div>
                    <div className="modalAction">
                        <button className="modBut" onClick={handleModalSave}>Save</button>
                        <button className="modBut" onClick={handleModalClose}>Cancel</button>
                    </div>
                </Modal>
            </ul>
        </div>


        <ToastContainer /> 
    </div>
);

}

`

STEP 19: Exporting the TodoList Component

At the end of the TodoList.js file, export the TodoList component:

`
export default TodoList;

`

Conclusion:

In this article, we have explored the code for building a to-do list app in React. We have seen how to manage tasks using state and local storage, handle user input, and implement features such as add task, view completed tasks, task deletion, editing, completion, setting time notifications and snooze feature.

We have also utilized the react-toastify library for displaying toast notifications and the react-modal library for implementing modals. By understanding and customizing this code, you can create your own task management application with added features and functionality.

By leveraging the power of React and the available libraries, you can build robust and interactive applications to enhance task management and productivity in various domains.
Please note that this code assumes you have the required CSS styles and audio files in place. Make sure to update the file paths accordingly or provide your own styles and audio files.

Good luck with your Todo List app!

react #javascript #beginners #reacthooks

Top comments (0)