From 35599e96249f11712ef0560e4d79989953e1d7ff Mon Sep 17 00:00:00 2001
From: Beng Guan <118246443+bengguankoay@users.noreply.github.com>
Date: Thu, 22 Jan 2026 15:53:31 +0800
Subject: [PATCH] feat(curriculum): add localStorage inspection step to "Build
a Todo App using Local Storage" course (#64609)
---
.../64fb29348a60361ccd45c1e2.md | 8 +-
.../64fb29348a60361ccd45c1e3.md | 407 ++++++++++++++++++
.../64fefebad99209211ec30537.md | 8 +-
.../64ff0313700dad264d19dfe4.md | 4 +-
.../64ff04cc33779427a6412449.md | 4 +-
.../64ff068e0426eb288874ed79.md | 4 +-
.../64ff24b80431f62ec6b93f65.md | 4 +-
.../65003986d17d1e1865b269c0.md | 4 +-
.../650046832f92c01a35834bca.md | 4 +-
.../650048b0764f9c1b798200e2.md | 4 +-
.../65004ba581d03d1d5628b41c.md | 4 +-
.../6632420f81f3cc554a5e540b.md | 4 +-
.../66ad0f178ed5791ed898fe56.md | 4 +-
.../671682cd6d7aa95f0078f35f.md | 4 +-
.../67168a7243b6396cb69c1bdf.md | 4 +-
.../structure/blocks/workshop-todo-app.json | 30 +-
16 files changed, 454 insertions(+), 47 deletions(-)
create mode 100644 curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e3.md
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e2.md b/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e2.md
index bae70fc9507..a853680830a 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e2.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e2.md
@@ -7,11 +7,9 @@ dashedName: step-53
# --description--
-`localStorage` offers methods for saving, retrieving, and deleting items. The items you save can be of any JavaScript data type.
+As you recall from previous lessons, `localStorage` is part of the Web Storage API that allows data to persist even after the browser window is closed or the page is refreshed.
-For instance, the `setItem()` method is used to save an item, and the `getItem()` method retrieves the item. To delete a specific item, you can utilize the `removeItem()` method, or if you want to delete all items in the storage, you can use `clear()`.
-
-Here's how you can save an item:
+Here is a reminder of how to set an item in `localStorage`:
```js
localStorage.setItem("key", value); // value could be string, number, or any other data type
@@ -19,8 +17,6 @@ localStorage.setItem("key", value); // value could be string, number, or any oth
A `myTaskArr` array has been provided for you. Use the `setItem()` method to save it with a key of `data`.
-After that, open your browser console and go to the `Applications` tab, select `Local Storage`, and the freeCodeCamp domain you see.
-
# --hints--
Your `localStorage.setItem()` should have a key of `"data"`.
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e3.md b/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e3.md
new file mode 100644
index 00000000000..bf55df7a279
--- /dev/null
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64fb29348a60361ccd45c1e3.md
@@ -0,0 +1,407 @@
+---
+id: 64fb29348a60361ccd45c1e3
+title: Step 54
+challengeType: 0
+dashedName: step-54
+---
+
+# --description--
+
+To view what's stored in `localStorage`, open the browser's developer tools and navigate to the local storage section:
+
+- **Chrome/Edge:** Open DevTools (F12), navigate to `Application` > `Storage` and expand `Local Storage`. Click a domain to view its key-value pairs.
+- **Firefox:** Open DevTools (F12), navigate to `Storage` and expand `Local Storage`. Click a domain to view its key-value pairs.
+- **Safari:** Choose `Safari` > `Settings`, and click `Advanced`. At the bottom of the pane, select the `Show Develop menu in menu bar` checkbox. Once the developer tools are enabled, right-click on the page within browser, select `Inspect element`, go to the `Storage` tab, then select `Local Storage`. Click a domain to view its key-value pairs.
+
+The data stored with the key `data` should be visible. Examine it and note what the values look like.
+
+Click the "Check Your Code" button to proceed to the next step.
+
+# --hints--
+
+This step has no tests. Click the "Check Your Code" button to continue.
+
+```js
+
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+localStorage.setItem("data", myTaskArr);
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
+
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64fefebad99209211ec30537.md b/curriculum/challenges/english/blocks/workshop-todo-app/64fefebad99209211ec30537.md
index 9c7d355e901..abdac5c2a1d 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/64fefebad99209211ec30537.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64fefebad99209211ec30537.md
@@ -1,15 +1,15 @@
---
id: 64fefebad99209211ec30537
-title: Step 54
+title: Step 55
challengeType: 0
-dashedName: step-54
+dashedName: step-55
---
# --description--
-If you check the "Application" tab of your browser console, you'll notice a series of `[object Object]`. This is because everything you save in `localStorage` needs to be in string format.
+When you inspect the stored value, you may notice entries that look like `[object Object],[object Object],[object Object]`. This happens because `localStorage` can only store values as strings.
-To resolve the issue, wrap the data you're saving in the `JSON.stringify()` method. Then, check local storage again to observe the results.
+To fix this, convert `data` to a string before saving it by using `JSON.stringify()`. After storing it again, recheck `localStorage` to see how the data now appears.
# --hints--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64ff0313700dad264d19dfe4.md b/curriculum/challenges/english/blocks/workshop-todo-app/64ff0313700dad264d19dfe4.md
index 8e7823ef2d8..578906c8407 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/64ff0313700dad264d19dfe4.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64ff0313700dad264d19dfe4.md
@@ -1,8 +1,8 @@
---
id: 64ff0313700dad264d19dfe4
-title: Step 55
+title: Step 56
challengeType: 0
-dashedName: step-55
+dashedName: step-56
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64ff04cc33779427a6412449.md b/curriculum/challenges/english/blocks/workshop-todo-app/64ff04cc33779427a6412449.md
index 9fb23d2b5f8..154d38bdc9a 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/64ff04cc33779427a6412449.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64ff04cc33779427a6412449.md
@@ -1,8 +1,8 @@
---
id: 64ff04cc33779427a6412449
-title: Step 56
+title: Step 57
challengeType: 0
-dashedName: step-56
+dashedName: step-57
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64ff068e0426eb288874ed79.md b/curriculum/challenges/english/blocks/workshop-todo-app/64ff068e0426eb288874ed79.md
index 0b8d8839dce..73f26aa5469 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/64ff068e0426eb288874ed79.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64ff068e0426eb288874ed79.md
@@ -1,8 +1,8 @@
---
id: 64ff068e0426eb288874ed79
-title: Step 57
+title: Step 58
challengeType: 0
-dashedName: step-57
+dashedName: step-58
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/64ff24b80431f62ec6b93f65.md b/curriculum/challenges/english/blocks/workshop-todo-app/64ff24b80431f62ec6b93f65.md
index 32849319c5e..d308188df0d 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/64ff24b80431f62ec6b93f65.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/64ff24b80431f62ec6b93f65.md
@@ -1,8 +1,8 @@
---
id: 64ff24b80431f62ec6b93f65
-title: Step 58
+title: Step 59
challengeType: 0
-dashedName: step-58
+dashedName: step-59
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/65003986d17d1e1865b269c0.md b/curriculum/challenges/english/blocks/workshop-todo-app/65003986d17d1e1865b269c0.md
index ab90657a91f..7a57da22bc8 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/65003986d17d1e1865b269c0.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/65003986d17d1e1865b269c0.md
@@ -1,8 +1,8 @@
---
id: 65003986d17d1e1865b269c0
-title: Step 59
+title: Step 60
challengeType: 0
-dashedName: step-59
+dashedName: step-60
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/650046832f92c01a35834bca.md b/curriculum/challenges/english/blocks/workshop-todo-app/650046832f92c01a35834bca.md
index 78f257954ec..a6f1ae09e97 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/650046832f92c01a35834bca.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/650046832f92c01a35834bca.md
@@ -1,8 +1,8 @@
---
id: 650046832f92c01a35834bca
-title: Step 60
+title: Step 61
challengeType: 0
-dashedName: step-60
+dashedName: step-61
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/650048b0764f9c1b798200e2.md b/curriculum/challenges/english/blocks/workshop-todo-app/650048b0764f9c1b798200e2.md
index 434231610b5..261a7be5965 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/650048b0764f9c1b798200e2.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/650048b0764f9c1b798200e2.md
@@ -1,8 +1,8 @@
---
id: 650048b0764f9c1b798200e2
-title: Step 61
+title: Step 62
challengeType: 0
-dashedName: step-61
+dashedName: step-62
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/65004ba581d03d1d5628b41c.md b/curriculum/challenges/english/blocks/workshop-todo-app/65004ba581d03d1d5628b41c.md
index 1efe5c0a385..1670f9b7ce0 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/65004ba581d03d1d5628b41c.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/65004ba581d03d1d5628b41c.md
@@ -1,8 +1,8 @@
---
id: 65004ba581d03d1d5628b41c
-title: Step 62
+title: Step 63
challengeType: 0
-dashedName: step-62
+dashedName: step-63
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/6632420f81f3cc554a5e540b.md b/curriculum/challenges/english/blocks/workshop-todo-app/6632420f81f3cc554a5e540b.md
index 1e6c4cd3b7c..17a88eddaf4 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/6632420f81f3cc554a5e540b.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/6632420f81f3cc554a5e540b.md
@@ -1,8 +1,8 @@
---
id: 6632420f81f3cc554a5e540b
-title: Step 63
+title: Step 64
challengeType: 0
-dashedName: step-63
+dashedName: step-64
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/66ad0f178ed5791ed898fe56.md b/curriculum/challenges/english/blocks/workshop-todo-app/66ad0f178ed5791ed898fe56.md
index 9ae23e3f4cc..27c13ed6ed9 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/66ad0f178ed5791ed898fe56.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/66ad0f178ed5791ed898fe56.md
@@ -1,8 +1,8 @@
---
id: 66ad0f178ed5791ed898fe56
-title: Step 64
+title: Step 65
challengeType: 0
-dashedName: step-64
+dashedName: step-65
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/671682cd6d7aa95f0078f35f.md b/curriculum/challenges/english/blocks/workshop-todo-app/671682cd6d7aa95f0078f35f.md
index 2e08e665fba..aa1a2a604e1 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/671682cd6d7aa95f0078f35f.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/671682cd6d7aa95f0078f35f.md
@@ -1,8 +1,8 @@
---
id: 671682cd6d7aa95f0078f35f
-title: Step 65
+title: Step 66
challengeType: 0
-dashedName: step-65
+dashedName: step-66
---
# --description--
diff --git a/curriculum/challenges/english/blocks/workshop-todo-app/67168a7243b6396cb69c1bdf.md b/curriculum/challenges/english/blocks/workshop-todo-app/67168a7243b6396cb69c1bdf.md
index b25926af3f4..155523eff85 100644
--- a/curriculum/challenges/english/blocks/workshop-todo-app/67168a7243b6396cb69c1bdf.md
+++ b/curriculum/challenges/english/blocks/workshop-todo-app/67168a7243b6396cb69c1bdf.md
@@ -1,8 +1,8 @@
---
id: 67168a7243b6396cb69c1bdf
-title: Step 66
+title: Step 67
challengeType: 0
-dashedName: step-66
+dashedName: step-67
---
# --description--
diff --git a/curriculum/structure/blocks/workshop-todo-app.json b/curriculum/structure/blocks/workshop-todo-app.json
index abeb2844ba7..44702932da6 100644
--- a/curriculum/structure/blocks/workshop-todo-app.json
+++ b/curriculum/structure/blocks/workshop-todo-app.json
@@ -220,56 +220,60 @@
"title": "Step 53"
},
{
- "id": "64fefebad99209211ec30537",
+ "id": "64fb29348a60361ccd45c1e3",
"title": "Step 54"
},
{
- "id": "64ff0313700dad264d19dfe4",
+ "id": "64fefebad99209211ec30537",
"title": "Step 55"
},
{
- "id": "64ff04cc33779427a6412449",
+ "id": "64ff0313700dad264d19dfe4",
"title": "Step 56"
},
{
- "id": "64ff068e0426eb288874ed79",
+ "id": "64ff04cc33779427a6412449",
"title": "Step 57"
},
{
- "id": "64ff24b80431f62ec6b93f65",
+ "id": "64ff068e0426eb288874ed79",
"title": "Step 58"
},
{
- "id": "65003986d17d1e1865b269c0",
+ "id": "64ff24b80431f62ec6b93f65",
"title": "Step 59"
},
{
- "id": "650046832f92c01a35834bca",
+ "id": "65003986d17d1e1865b269c0",
"title": "Step 60"
},
{
- "id": "650048b0764f9c1b798200e2",
+ "id": "650046832f92c01a35834bca",
"title": "Step 61"
},
{
- "id": "65004ba581d03d1d5628b41c",
+ "id": "650048b0764f9c1b798200e2",
"title": "Step 62"
},
{
- "id": "6632420f81f3cc554a5e540b",
+ "id": "65004ba581d03d1d5628b41c",
"title": "Step 63"
},
{
- "id": "66ad0f178ed5791ed898fe56",
+ "id": "6632420f81f3cc554a5e540b",
"title": "Step 64"
},
{
- "id": "671682cd6d7aa95f0078f35f",
+ "id": "66ad0f178ed5791ed898fe56",
"title": "Step 65"
},
{
- "id": "67168a7243b6396cb69c1bdf",
+ "id": "671682cd6d7aa95f0078f35f",
"title": "Step 66"
+ },
+ {
+ "id": "67168a7243b6396cb69c1bdf",
+ "title": "Step 67"
}
],
"helpCategory": "JavaScript"