30 Days of JavaScript: Building a Simple ToDo App with JavaScript, Day 8

30 Days of JavaScript: Building a Simple Weather App
30 Days of JavaScript: Building a Simple ToDo App with JavaScript, Day 8

Create a fully functional ToDo app using JavaScript. Master CRUD operations, data persistence with LocalStorage, and practical coding techniques in this step-by-step guide.

You’ve spent a week on syntax and small exercises. Today you build something you’d actually keep in a tab: a ToDo app that remembers your tasks after you close the browser. DOM work, event handling, a first real look at storage, it all comes together in one small app that runs. Let’s build it.

Table of Contents

Planning and Structuring Your App

Before you write a line of JavaScript, get clear on what you’re building. The app has three moving parts:

  • An input field for adding new tasks
  • A task list to display tasks
  • Buttons for editing and deleting tasks

That’s the whole surface. HTML gives it structure, a little CSS makes it look decent, and JavaScript does the work.

Implementing CRUD Operations

CRUD is Create, Read, Update, Delete: the four things nearly every app does with data. Your ToDo app is a small, honest version of all four, so we’ll take them one at a time.

Create

Start with an input to type into and a button to submit it:

HTML
<input type="text" id="task-input" placeholder="Enter a task">
<button id="add-task-btn">Add Task</button>

Then wire up a click handler. It reads the input, trims the whitespace, creates the task, and clears the field so you’re ready for the next one:

JS
/**
 * Adds a new task when the button is clicked
 * @param {string} taskText - The text for the new task
 */
const taskInput = document.getElementById('task-input');
const addTaskBtn = document.getElementById('add-task-btn');
addTaskBtn.addEventListener('click', () => {
    const taskText = taskInput.value.trim();
    if (taskText !== '') {
        createTask(taskText);
        taskInput.value = '';
        saveTasks(); // Save to LocalStorage after creating the task
    }
});
Read

Tasks need a home on the page. An unordered list does the job:

HTML
<ul id="task-list"></ul>

Now a function that builds a single task and drops it into that list:

JS
/**
 * Creates and displays a task
 * @param {string} text - The text of the task
 */
const taskList = document.getElementById('task-list');
function createTask(text) {
    const listItem = document.createElement('li');
    listItem.textContent = text;
    addTaskButtons(listItem); // Add edit and delete buttons
    taskList.appendChild(listItem);
}
Update and Delete

Every task gets two buttons: one to edit, one to delete. Here’s the function that adds them, along with the handler behind each one. One honest note: prompt() and confirm() are the blunt way to ask the user something. They’re fine while you’re learning, but in a real app you’d swap them for your own UI.

JS
/**
 * Adds edit and delete buttons to a task
 * @param {HTMLElement} listItem - The list item representing the task
 */
function addTaskButtons(listItem) {
    const editBtn = document.createElement('button');
    editBtn.textContent = 'Edit';
    editBtn.addEventListener('click', () => editTask(listItem));
    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = 'Delete';
    deleteBtn.addEventListener('click', () => deleteTask(listItem));
    listItem.appendChild(editBtn);
    listItem.appendChild(deleteBtn);
}
/**
 * Edits a task
 * @param {HTMLElement} listItem - The task item to be edited
 */
function editTask(listItem) {
    const updatedText = prompt('Edit the task', listItem.firstChild.textContent);
    if (updatedText !== null && updatedText.trim() !== '') {
        listItem.firstChild.textContent = updatedText.trim();
        saveTasks(); // Save changes to LocalStorage
    }
}
/**
 * Deletes a task
 * @param {HTMLElement} listItem - The task item to be deleted
 */
function deleteTask(listItem) {
    if (confirm('Are you sure you want to delete this task?')) {
        listItem.remove();
        saveTasks(); // Update LocalStorage after deletion
    }
}

LocalStorage for Data Persistence

Right now, refresh the page and everything vanishes. LocalStorage fixes that. One thing to know up front: LocalStorage only ever stores strings, so we turn the task array into a string with JSON.stringify on the way in, and JSON.parse on the way back out.

JS
/**
 * Saves tasks to LocalStorage
 */
function saveTasks() {
    const tasks = [];
    const listItems = taskList.getElementsByTagName('li');
    for (let item of listItems) {
        tasks.push(item.firstChild.textContent); // Store task text only
    }
    localStorage.setItem('tasks', JSON.stringify(tasks));
}

getElementsByTagName returns a live list that stays in sync with the DOM. We’re only reading from it here, so looping over it is safe. Call saveTasks() every time the list changes, on create, edit, and delete:

JS
// Save tasks after creating a task
createTask(taskText);
saveTasks();
// Save tasks after editing a task
listItem.firstChild.textContent = updatedText.trim();
saveTasks();
// Save tasks after deleting a task
listItem.remove();
saveTasks();

Finally, load whatever’s stored when the page opens, so your tasks are waiting for you:

JS
/**
 * Loads tasks from LocalStorage on page load
 */
function loadTasks() {
    const tasks = JSON.parse(localStorage.getItem('tasks'));
    if (tasks) {
        tasks.forEach(task => createTask(task)); // Re-create tasks on load
    }
}
document.addEventListener('DOMContentLoaded', loadTasks); // Load tasks when page loads

Wrapping Up

Here’s the whole thing in one file. Save it with an .html extension, open it in your browser, and it works:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Simple ToDo App</title>
    <style>
        /* Add your custom styles here */
    </style>
</head>
<body>
    <h1>Simple ToDo App</h1>
    <input type="text" id="task-input" placeholder="Enter a task"/>
    <button id="add-task-btn">Add Task</button>
    <ul id="task-list"></ul>
    <script>
    /**
     * References to DOM elements for task input and buttons
     */
    const taskInput = document.getElementById('task-input');
    const addTaskBtn = document.getElementById('add-task-btn');
    const taskList = document.getElementById('task-list');
    /**
     * Event listener to handle adding a new task when the button is clicked
     */
    addTaskBtn.addEventListener('click', () => {
        const taskText = taskInput.value.trim();
        if (taskText) {
            createTask(taskText);   // Create a new task
            saveTasks();            // Save to LocalStorage after task creation
            taskInput.value = '';    // Clear input field after task creation
        }
    });
    /**
     * Creates a new task list item and adds edit/delete buttons
     * @param {string} text - The text of the new task
     */
    function createTask(text) {
        const listItem = document.createElement('li');
        listItem.textContent = text;
        addTaskButtons(listItem);    // Add Edit and Delete buttons to task
        taskList.appendChild(listItem);
    }
    /**
     * Adds Edit and Delete buttons to a task list item
     * @param {HTMLElement} listItem - The list item to add buttons to
     */
    function addTaskButtons(listItem) {
        const editBtn = document.createElement('button');
        editBtn.textContent = 'Edit';
        editBtn.addEventListener('click', () => editTask(listItem));
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = 'Delete';
        deleteBtn.addEventListener('click', () => deleteTask(listItem));
        listItem.appendChild(editBtn);
        listItem.appendChild(deleteBtn);
    }
    /**
     * Edits the task text after prompting the user
     * @param {HTMLElement} listItem - The task list item to edit
     */
    function editTask(listItem) {
        const updatedText = prompt('Edit the task', listItem.firstChild.textContent);
        if (updatedText) {
            listItem.firstChild.textContent = updatedText.trim();
            saveTasks();    // Save changes after editing the task
        }
    }
    /**
     * Deletes a task list item after confirming with the user
     * @param {HTMLElement} listItem - The task list item to delete
     */
    function deleteTask(listItem) {
        if (confirm('Are you sure you want to delete this task?')) {
            listItem.remove();   // Remove task from DOM
            saveTasks();         // Save the updated task list
        }
    }
    /**
     * Saves the current list of tasks to LocalStorage
     */
    function saveTasks() {
        const tasks = [];
        const listItems = taskList.getElementsByTagName('li');
        for (let item of listItems) {
            tasks.push(item.firstChild.textContent);   // Push task text to array
        }
        localStorage.setItem('tasks', JSON.stringify(tasks));    // Save tasks to LocalStorage
    }
    /**
     * Loads tasks from LocalStorage and displays them on page load
     */
    function loadTasks() {
        const tasks = JSON.parse(localStorage.getItem('tasks'));
        if (tasks) {
            tasks.forEach(task => createTask(task));   // Recreate tasks on page load
        }
    }
    // Load tasks from LocalStorage when the document is loaded
    document.addEventListener('DOMContentLoaded', loadTasks);
    </script>
</body>
</html>
Conclusion

You just built a real app. It creates, reads, updates, and deletes tasks, and the data survives a refresh. That’s the same shape as software far bigger than this, minus the framework.

Want to push it further? Add due dates, categories, or a way to mark a task done. Heads-up: the current version saves only the task text, so a done state means storing more than a plain string per task. Small change to the code you already have.

What’s Next?

In Day 9, we’ll get into regular expressions. You’ll use them to match patterns, validate input, and reshape text. It feels like magic once it clicks. See you there.

Next: 30 Days of JavaScript: Regular Expressions in JavaScript, Day 9

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top