JavaScript Mini Project — Interactive To-Do
This page builds a short to-do list. You add buy tea, mark that item done, and remove it. A finished item reads done: buy tea. An open item reads buy tea.
Not the summary
The summary named the skills and showed no list. Here the list is the page. You will use an array, a form, textContent, and click — and you will not reload the page on each add.
One task is an object
tasks is an array. Each item has text and done. A new item starts with done: false. addTask trims the words. A blank string is not a task, so it returns without pushing.
const tasks = [];
function addTask(text) {
const clean = text.trim();
if (!clean) {
return;
}
tasks.push({ text: clean, done: false });
}
addTask("buy tea");
console.log(tasks[0].text);
console.log(tasks[0].done);
The logs are buy tea, then false. addTask(" ") trims to an empty string and pushes nothing. tasks.length stays 0 in that case.
The form must not reload
A submit reloads the document. The array lives in memory, so a reload throws it away and the list looks empty. event.preventDefault() stops that reload. Then add the trimmed text, clear the field, and draw the list again.
const form = document.querySelector("#add-form");
const input = document.querySelector("#task-text");
form.addEventListener("submit", (event) => {
event.preventDefault();
addTask(input.value);
input.value = "";
render();
});
The document stays. The field is empty again. render runs with the new array. If the field was only spaces, addTask returns and the list does not grow.
Draw, mark, remove
render empties #tasks, then makes one li per item. An open item shows text. A done item shows done: plus text. Each row has two buttons, both type="button", so they do not submit the form. Done flips done. Remove uses splice. Both call render again, because the indexes in the old buttons would be wrong after a removal.
function toggleTask(index) {
tasks[index].done = !tasks[index].done;
}
function removeTask(index) {
tasks.splice(index, 1);
}
function render() {
const list = document.querySelector("#tasks");
list.textContent = "";
tasks.forEach((task, index) => {
const item = document.createElement("li");
item.textContent = task.done ? "done: " + task.text : task.text;
const doneButton = document.createElement("button");
doneButton.type = "button";
doneButton.textContent = "Done";
doneButton.addEventListener("click", () => {
toggleTask(index);
render();
});
const removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.textContent = "Remove";
removeButton.addEventListener("click", () => {
removeTask(index);
render();
});
item.append(doneButton, removeButton);
list.append(item);
});
}
Start from addTask("buy tea"), then toggleTask(0), then render. The row reads done: buy tea. toggleTask(0) again makes it buy tea. removeTask(0) leaves tasks.length at 0, and the next render shows no rows. Write the words with textContent, not with HTML pasted from the field.
Indexes belong to the latest draw
forEach closes over the index from that pass. After splice, those old numbers point at the wrong item. Calling render throws the old buttons away and creates new ones with the current indexes. Skip that second render and Remove deletes the wrong row.
The whole page
The form adds. The list draws. Done and Remove only change tasks, then draw again.
<!DOCTYPE html>
<html>
<body>
<form id="add-form">
<input id="task-text" type="text">
<button type="submit">Add</button>
</form>
<ul id="tasks"></ul>
<script>
const tasks = [];
function addTask(text) {
const clean = text.trim();
if (!clean) return;
tasks.push({ text: clean, done: false });
}
function toggleTask(index) {
tasks[index].done = !tasks[index].done;
}
function removeTask(index) {
tasks.splice(index, 1);
}
function render() {
const list = document.querySelector("#tasks");
list.textContent = "";
tasks.forEach((task, index) => {
const item = document.createElement("li");
item.textContent = task.done ? "done: " + task.text : task.text;
const doneButton = document.createElement("button");
doneButton.type = "button";
doneButton.textContent = "Done";
doneButton.addEventListener("click", () => {
toggleTask(index);
render();
});
const removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.textContent = "Remove";
removeButton.addEventListener("click", () => {
removeTask(index);
render();
});
item.append(doneButton, removeButton);
list.append(item);
});
}
document.querySelector("#add-form").addEventListener("submit", (event) => {
event.preventDefault();
addTask(document.querySelector("#task-text").value);
document.querySelector("#task-text").value = "";
render();
});
</script>
</body>
</html>
Type buy tea and press Add. One row reads buy tea, and the field is clear. Press Done on that row. The same row reads done: buy tea. Press Remove. The list is empty. Press Add on a blank field and nothing appears.
At the counter
- Call addTask("buy tea"). The first text is buy tea and done is false.
- Submit the form. The page does not reload, and the row appears.
- Press Done. The row reads done: buy tea.
- Press Remove. tasks.length is 0.
- Submit spaces only. The list does not grow.
Misses
A reload is not a save. A blank field is not a task. Done and Remove must not submit the form.
- Forgetting
preventDefault, so the array dies on submit - Pushing an empty string
- Using
type="submit"on Done or Remove - Deleting a row in the page and forgetting
splice, so the next draw brings it back - Skipping
renderaftersplice, so the old index removes the wrong item - Writing the field into the page as HTML
Keep this
- A task is
{ text, done }, and done starts false addTask("buy tea")stores buy tea; a blank stores nothingpreventDefaultkeeps the page, thenrenderdraws the rows- Done shows done: buy tea; Remove leaves length 0
- Draw again after every change
🧠 Test Your Knowledge
Test Your Knowledge
Challenge yourself with this interactive quiz and see how well you understand the topic
📝 Instructions
- Read each question carefully
- Select the best answer for each question
- You can retake the quiz as many times as you want
- Your progress will be shown at the top