Tell us what’s happening:
When you click #add-bookmark-button-form , you should update the bookmarks key stored in the local storage by adding an object to the end of the array. The added object should have name set to the value of the #name input, category set to the value of the selected option from the category dropdown, and url set to the value of the #url input.
My code isn’t complete yet, but i can’t pass this step.
Also can in add another property in my bookmarkObj or does it have to be only these 3.
Your code so far
/* file: script.js */
const mainSection = document.getElementById("main-section");
const categoryDropdown = document.getElementById("category-dropdown");
const viewCategoryBtn = document.getElementById("view-category-button");
const addBookmarkBtn = document.getElementById("add-bookmark-button");
const closeFormBtn = document.getElementById("close-form-button");
const formSection = document.getElementById("form-section");
const categoryName = document.querySelectorAll(".category-name");
const formName = document.getElementById("name");
const formURL = document.getElementById("url");
const formAddBookmarkBtn = document.getElementById("add-bookmark-button-form");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryNameInList = document.getElementById("category-name");
const categoryList = document.getElementById("category-list");
const closeListBtn = document.getElementById("close-list-button");
const deleteBookmarkBtn = document.getElementById("delete-bookmark-button");
function getBookmarks() {
try {
const bookmarks = JSON.parse(localStorage.getItem("bookmarks"));
let result = bookmarks.every(bookmark => bookmark.name && bookmark.category && bookmark.url);
return result ? bookmarks : [];
} catch (error) {
return [];
}
}
let bookMarks = getBookmarks();
const currentCategoryBookmarks = [];
function changeCategoryName() {
let str = categoryDropdown.value;
let firstLet = str[0].toUpperCase();
categoryName.forEach(elem => elem.innerText = firstLet + str.slice(1, str.length));
}
function displayOrCloseForm() {
mainSection.classList.toggle("hidden");
formSection.classList.toggle("hidden");
}
addBookmarkBtn.addEventListener("click", () => {
displayOrCloseForm();
changeCategoryName();
});
closeFormBtn.addEventListener("click", displayOrCloseForm);
formAddBookmarkBtn.addEventListener("click", () => {
if (formName.value.lenght === 0 || formURL.value.length === 0) {
alert("Please, provide valid name and URL");
return;
}
const bookmarkObj = {
name: formName.value,
category: categoryDropdown.value,
url: formURL.value
}
bookMarks.push(bookmarkObj);
localStorage.setItem("bookmarks", JSON.stringify(bookMarks));
formName.value = "";
formURL.value = "";
displayOrCloseForm();
});
function displayOrHideCategory() {
mainSection.classList.toggle("hidden");
bookmarkListSection.classList.toggle("hidden");
}
viewCategoryBtn.addEventListener("click", () => {
displayOrHideCategory()
changeCategoryName();
let categoryBookMs = bookMarks.filter(bookmark => bookmark.category === categoryDropdown.value);
if (!categoryBookMs.length) {
categoryList.innerHTML = `<p>No Bookmarks Found</p>`
} else {
categoryBookMs.forEach(({name, url}) => {
categoryList.innerHTML += `
<div>
<input type="radio" name="bookmark" id="${name}" value="${name}" />
<label for="${name}">
<a href="${url}">${name}</a>
</label>"`
})
}
});
closeListBtn.addEventListener("click", () => {
categoryList.innerHTML = "";
displayOrHideCategory();
});
<!-- file: index.html -->
/* file: styles.css */
Your browser information:
User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36
Challenge Information:
Build a Bookmark Manager App - Build a Bookmark Manager App
GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-bookmark-manager-app/66def5467aee701733aaf8cc.md at main · freeCodeCamp/freeCodeCamp · GitHub
My code is this right now.
I am not able to pass 11, 18, 19, 20, 22, 23.
/* file: script.js */
const mainSection = document.getElementById("main-section");
const categoryDropdown = document.getElementById("category-dropdown");
const viewCategoryBtn = document.getElementById("view-category-button");
const addBookmarkBtn = document.getElementById("add-bookmark-button");
const closeFormBtn = document.getElementById("close-form-button");
const formSection = document.getElementById("form-section");
const categoryName = document.querySelectorAll(".category-name");
const formName = document.getElementById("name");
const formURL = document.getElementById("url");
const formAddBookmarkBtn = document.getElementById("add-bookmark-button-form");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryNameInList = document.getElementById("category-name");
const categoryList = document.getElementById("category-list");
const closeListBtn = document.getElementById("close-list-button");
const deleteBookmarkBtn = document.getElementById("delete-bookmark-button");
function getBookmarks() {
try {
const bookmarks = JSON.parse(localStorage.getItem("bookmarks"));
let result = bookmarks.every(bookmark => bookmark.name && bookmark.category && bookmark.url);
return result ? bookmarks : [];
} catch (error) {
return [];
}
}
let bookMarks = getBookmarks();
const currentCategoryBookmarks = [];
function changeCategoryName() {
let str = categoryDropdown.value;
let firstLet = str[0].toUpperCase();
categoryName.forEach(elem => elem.innerText = firstLet + str.slice(1, str.length));
}
function displayOrCloseForm() {
mainSection.classList.toggle("hidden");
formSection.classList.toggle("hidden");
}
addBookmarkBtn.addEventListener("click", () => {
displayOrCloseForm();
changeCategoryName();
});
closeFormBtn.addEventListener("click", displayOrCloseForm);
formAddBookmarkBtn.addEventListener("click", () => {
const bookmarkObj = {
name: formName.value,
category: categoryDropdown.value,
url: formURL.value
}
if (!formName.value.length|| !formURL.value.length) {
alert("Please, provide valid name and URL");
} else {
bookMarks.push(bookmarkObj);
localStorage.setItem("bookmarks", JSON.stringify(bookMarks));
formName.value = "";
formURL.value = "";
displayOrCloseForm();
}
});
function displayOrHideCategory() {
mainSection.classList.toggle("hidden");
bookmarkListSection.classList.toggle("hidden");
}
function fillViewCategoryList() {
let categoryBookMs = bookMarks.filter(bookmark => bookmark.category === categoryDropdown.value);
if (!categoryBookMs.length) {
categoryList.innerHTML = `<p>No Bookmarks Found</p>`
} else {
categoryBookMs.forEach(({name, url}) => {
categoryList.innerHTML += `
<div>
<input type="radio" name="bookmark" id="${name}" value="${name}" />
<label for="${name}">
<a href="${url}">${name}</a>
</label>`
})
}
}
viewCategoryBtn.addEventListener("click", () => {
displayOrHideCategory()
changeCategoryName();
fillViewCategoryList();
});
closeListBtn.addEventListener("click", () => {
categoryList.innerHTML = "";
displayOrHideCategory();
});
categoryList.querySelectorAll("label").forEach(elem => elem.addEventListener("click", (e) => {
e.target.color = "green";
}))
deleteBookmarkBtn.addEventListener("click", () => {
const delInput = categoryList.querySelectorAll('input');
delInput.forEach(elem => {
if (elem.checked) {
let delIndex = bookMarks.findIndex(obj => obj.name === elem.getAttribute('id'));
bookMarks.splice(delIndex, 1);
localStorage.setItem("bookmarks", JSON.stringify(bookMarks));
}
})
categoryList.innerHTML = "";
fillViewCategoryList();
})
Hey @BHAT_AAQIB ,
One major issue which I noticed in your code is that you are accessing bookMarks in functions and event listeners.
The problem is that you are fetching data from localStorage once at start, but localStorage keeps updating throughout the code while bookMarks stays frozen at the initial point, meaning it never stays in sync with latest localStorage data.
So instead of using bookMarks, you should use getBookmarks() every time you need the list, as it always fetches the latest data fresh from localStorage.
dhess
June 20, 2026, 12:19pm
4
Hi @BHAT_AAQIB ,
I recommend using your getBookmarks function inside your functions to get a fresh copy rather than relying on a global variable.
Happy coding
Why can’t i pass 19 and 20 now.
const categoryDropdown = document.getElementById("category-dropdown");
const viewCategoryBtn = document.getElementById("view-category-button");
const addBookmarkBtn = document.getElementById("add-bookmark-button");
const closeFormBtn = document.getElementById("close-form-button");
const formSection = document.getElementById("form-section");
const categoryName = document.querySelectorAll(".category-name");
const formName = document.getElementById("name");
const formURL = document.getElementById("url");
const formAddBookmarkBtn = document.getElementById("add-bookmark-button-form");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryNameInList = document.getElementById("category-name");
const categoryList = document.getElementById("category-list");
const closeListBtn = document.getElementById("close-list-button");
const deleteBookmarkBtn = document.getElementById("delete-bookmark-button");
function getBookmarks() {
try {
const bookmarks = JSON.parse(localStorage.getItem("bookmarks"));
let result = bookmarks.every(bookmark => bookmark.name && bookmark.category && bookmark.url);
return result ? bookmarks : [];
} catch (error) {
return [];
}
}
const currentCategoryBookmarks = [];
function changeCategoryName() {
let str = categoryDropdown.value;
let firstLet = str[0].toUpperCase();
categoryName.forEach(elem => elem.innerText = firstLet + str.slice(1, str.length));
}
function displayOrCloseForm() {
mainSection.classList.toggle("hidden");
formSection.classList.toggle("hidden");
}
addBookmarkBtn.addEventListener("click", () => {
displayOrCloseForm();
changeCategoryName();
});
closeFormBtn.addEventListener("click", displayOrCloseForm);
formAddBookmarkBtn.addEventListener("click", () => {
const bookmarkObj = {
name: formName.value,
category: categoryDropdown.value,
url: formURL.value
}
if (!formName.value.length|| !formURL.value.length) {
alert("Please, provide valid name and URL");
} else {
let bookmark = getBookmarks();
bookmark.push(bookmarkObj);
console.log(bookmark)
localStorage.setItem("bookmarks", JSON.stringify(bookmark));
formName.value = "";
formURL.value = "";
displayOrCloseForm();
}
});
function displayOrHideCategory() {
mainSection.classList.toggle("hidden");
bookmarkListSection.classList.toggle("hidden");
}
function fillViewCategoryList() {
let categoryBookMs = getBookmarks().filter(bookmark => bookmark.category === categoryDropdown.value);
if (!categoryBookMs.length) {
categoryList.innerHTML = `<p>No Bookmarks Found</p>`
} else {
categoryBookMs.forEach(({name, url}) => {
categoryList.innerHTML += `<div>
<input type="radio" name="bookmark" id="${name}" value="${name}" />
<label for="${name}">
<a href="${url}">${name}</a>
</label>
</div>`
})
}
}
viewCategoryBtn.addEventListener("click", () => {
displayOrHideCategory()
changeCategoryName();
fillViewCategoryList();
});
closeListBtn.addEventListener("click", () => {
categoryList.innerHTML = "";
displayOrHideCategory();
});
categoryList.querySelectorAll("label").forEach(elem => elem.addEventListener("click", (e) => {
e.target.color = "green";
}))
deleteBookmarkBtn.addEventListener("click", () => {
const delInput = categoryList.querySelectorAll('input');
delInput.forEach(elem => {
if (elem.checked) {
let bookmark = getBookmarks();
let delIndex = bookmark.findIndex(obj => obj.name === elem.getAttribute('id'));
bookmark.splice(delIndex, 1);
localStorage.setItem("bookmarks", JSON.stringify(bookmark));
}
})
categoryList.innerHTML = "";
fillViewCategoryList();
})
dhess
June 20, 2026, 1:38pm
6
What are you doing to test your code? When I click the “View Category” button, I see an error in the console:
Uncaught ReferenceError: mainSection is not defined
Sorry i didnt copy mainSection line.
const mainSection = document.getElementById("main-section");
const categoryDropdown = document.getElementById("category-dropdown");
const viewCategoryBtn = document.getElementById("view-category-button");
const addBookmarkBtn = document.getElementById("add-bookmark-button");
const closeFormBtn = document.getElementById("close-form-button");
const formSection = document.getElementById("form-section");
const categoryName = document.querySelectorAll(".category-name");
const formName = document.getElementById("name");
const formURL = document.getElementById("url");
const formAddBookmarkBtn = document.getElementById("add-bookmark-button-form");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryNameInList = document.getElementById("category-name");
const categoryList = document.getElementById("category-list");
const closeListBtn = document.getElementById("close-list-button");
const deleteBookmarkBtn = document.getElementById("delete-bookmark-button");
function getBookmarks() {
try {
const bookmarks = JSON.parse(localStorage.getItem("bookmarks"));
let result = bookmarks.every(bookmark => bookmark.name && bookmark.category && bookmark.url);
return result ? bookmarks : [];
} catch (error) {
return [];
}
}
const currentCategoryBookmarks = [];
function changeCategoryName() {
let str = categoryDropdown.value;
let firstLet = str[0].toUpperCase();
categoryName.forEach(elem => elem.innerText = firstLet + str.slice(1, str.length));
}
function displayOrCloseForm() {
mainSection.classList.toggle("hidden");
formSection.classList.toggle("hidden");
}
addBookmarkBtn.addEventListener("click", () => {
displayOrCloseForm();
changeCategoryName();
});
closeFormBtn.addEventListener("click", displayOrCloseForm);
formAddBookmarkBtn.addEventListener("click", () => {
const bookmarkObj = {
name: formName.value,
category: categoryDropdown.value,
url: formURL.value
}
if (!formName.value.length|| !formURL.value.length) {
alert("Please, provide valid name and URL");
} else {
let bookmark = getBookmarks();
bookmark.push(bookmarkObj);
console.log(bookmark)
localStorage.setItem("bookmarks", JSON.stringify(bookmark));
formName.value = "";
formURL.value = "";
displayOrCloseForm();
}
});
function displayOrHideCategory() {
mainSection.classList.toggle("hidden");
bookmarkListSection.classList.toggle("hidden");
}
function fillViewCategoryList() {
let bookmarks = getBookmarks();
let categoryBookMs = bookmarks.filter(bookmark => bookmark.category === categoryDropdown.value);
if (!categoryBookMs.length) {
categoryList.innerHTML = `<p>No Bookmarks Found</p>`
} else {
categoryBookMs.forEach(({name, url}) => {
categoryList.innerHTML += `<div>
<input type="radio" name="bookmark" id="${name}" value="${name}" />
<label for="${name}">
<a href="${url}">${name}</a>
</label>
</div>`
});
}
}
viewCategoryBtn.addEventListener("click", () => {
displayOrHideCategory()
changeCategoryName();
fillViewCategoryList();
});
closeListBtn.addEventListener("click", () => {
categoryList.innerHTML = "";
displayOrHideCategory();
});
categoryList.querySelectorAll("label").forEach(elem => elem.addEventListener("click", (e) => {
e.target.color = "green";
}))
deleteBookmarkBtn.addEventListener("click", () => {
const delInput = categoryList.querySelectorAll('input');
delInput.forEach(elem => {
if (elem.checked) {
let bookmark = getBookmarks();
let delIndex = bookmark.findIndex(obj => obj.name === elem.getAttribute('id'));
bookmark.splice(delIndex, 1);
localStorage.setItem("bookmarks", JSON.stringify(bookmark));
}
})
categoryList.innerHTML = "";
fillViewCategoryList();
})
This loop is problematic, each iteration re-parses and rewrites the entire DOM. You should build the full HTML string first and then set innerHTML.
Thanks that fixed the HTML problem.
Can you please explian what difference it made, cause i have been using the same method to add HTML in the previous labs and workshops.
I can’t pass the last one which is the delete button one.
The code is working as it should but test isn’t passing.
Thanks again.
const mainSection = document.getElementById("main-section");
const categoryDropdown = document.getElementById("category-dropdown");
const viewCategoryBtn = document.getElementById("view-category-button");
const addBookmarkBtn = document.getElementById("add-bookmark-button");
const closeFormBtn = document.getElementById("close-form-button");
const formSection = document.getElementById("form-section");
const categoryName = document.querySelectorAll(".category-name");
const formName = document.getElementById("name");
const formURL = document.getElementById("url");
const formAddBookmarkBtn = document.getElementById("add-bookmark-button-form");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryNameInList = document.getElementById("category-name");
const categoryList = document.getElementById("category-list");
const closeListBtn = document.getElementById("close-list-button");
const deleteBookmarkBtn = document.getElementById("delete-bookmark-button");
function getBookmarks() {
try {
const bookmarks = JSON.parse(localStorage.getItem("bookmarks"));
let result = bookmarks.every(bookmark => bookmark.name && bookmark.category && bookmark.url);
return result ? bookmarks : [];
} catch (error) {
return [];
}
}
const currentCategoryBookmarks = [];
function changeCategoryName() {
let str = categoryDropdown.value;
let firstLet = str[0].toUpperCase();
categoryName.forEach(elem => elem.innerText = firstLet + str.slice(1, str.length));
}
function displayOrCloseForm() {
mainSection.classList.toggle("hidden");
formSection.classList.toggle("hidden");
}
addBookmarkBtn.addEventListener("click", () => {
displayOrCloseForm();
changeCategoryName();
});
closeFormBtn.addEventListener("click", displayOrCloseForm);
formAddBookmarkBtn.addEventListener("click", () => {
const bookmarkObj = {
name: formName.value,
category: categoryDropdown.value,
url: formURL.value
}
if (!formName.value.length|| !formURL.value.length) {
alert("Please, provide valid name and URL");
} else {
let bookmark = getBookmarks();
bookmark.push(bookmarkObj);
localStorage.setItem("bookmarks", JSON.stringify(bookmark));
formName.value = "";
formURL.value = "";
displayOrCloseForm();
}
});
function displayOrHideCategory() {
mainSection.classList.toggle("hidden");
bookmarkListSection.classList.toggle("hidden");
}
function fillViewCategoryList() {
let bookmarks = getBookmarks();
let categoryBookMs = bookmarks.filter(bookmark => bookmark.category === categoryDropdown.value);
if (!categoryBookMs.length) {
categoryList.innerHTML = `<p>No Bookmarks Found</p>`
} else {
let fullHTML = ``;
categoryBookMs.forEach(({name, url}) => {
fullHTML += `<div>
<input type="radio" name="bookmark" id="${name}" value="${name}" />
<label for="${name}">
<a href="${url}">${name}</a>
</label>
</div>`
});
categoryList.innerHTML = fullHTML;
}
}
viewCategoryBtn.addEventListener("click", () => {
displayOrHideCategory()
changeCategoryName();
fillViewCategoryList();
});
closeListBtn.addEventListener("click", () => {
categoryList.innerHTML = "";
displayOrHideCategory();
});
categoryList.querySelectorAll("label").forEach(elem => elem.addEventListener("click", (e) => {
e.target.color = "green";
}))
deleteBookmarkBtn.addEventListener("click", () => {
const delInput = categoryList.querySelectorAll('input');
delInput.forEach(elem => {
if (elem.checked) {
let bookmark = getBookmarks();
let delIndex = bookmark.findIndex(obj => obj.name === elem.getAttribute('id'));
bookmark.splice(delIndex, 1);
localStorage.setItem("bookmarks", JSON.stringify(bookmark));
fillViewCategoryList();
}
})
})
innerHTML += is like read + append + reprase, which can preserve and duplicate existing DOM, for e.g. its like appending to a shopping list by rewriting the entire list every time, but accidentally copying the old list twice because you didn’t erase it first.
For second approach, its like write a clean new list on fresh piece of paper, no change of old items hanging around and therefore not messing with tests.
Over here, you are only matching name but two categories can have same name and therefore you might delete the wrong one
Also you should put a safeguard incase you don’t find the index. delIndex !== -1
dhess
June 20, 2026, 4:09pm
11
I’d like to see you logging inside your functions so you can debug yourself. For example, if you had logged categoryList.innerHTML inside your categoryBookMs.forEach loop, you would have seen the duplications in your browser’s console. You can also see assertion errors in your browser’s console after running the tests, which can be very helpful to debug your code.