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

+
+ + + +
+

Discard unsaved changes?

+
+ + +
+
+
+
+
+
+ + + + +``` + +```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}

+ + +
+ ` + } + ); +}; + + +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"