I am attempting to make an eCommerce site from scratch. Right now I have the following code that gets all the items from a Firebase Firestore database and then adds HTML into an items div element based on the item's info.
var items = [];
var itemsDiv = document.getElementsByClassName("items")[0];
db.collection("items").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
var docData = doc.data();
items.push(docData);
});
renderItems();
});
function renderItems() {
console.log(items);
items.forEach(function(item) {
itemsDiv.innerHTML += `
<div class="item">
<img class="itemImgBackground" src="assets/${item.name.replace(" ", "")}.png">
<img class="itemImg" src="assets/${item.name.replace(" ", "")}.png">
<span class="itemName">${item.name}</span>
<span class="itemCondition">${item.condition}</span>
<span class="itemPrice">${item.price}</span>
</div>
`
});
}
What can I do to make my code better?
2 Answers 2
Here are a few things:
good you're using
`
(template literals) to format your HTML inside the JavaScript codedon't use
innerHTML +=
as it forces the browser to re-render the DOM when called. Read more here: Why isElement.innerHTML +=
bad code?. Instead create an HTML element with.createElement
and append it to an existing DOM element.appendChild
.So instead of appending the the HTML string
<div class="item">...</div>
to the parent div you can:create a wrapping
div
element:const div = document.createElement('div');
append the HTML string to the div wrapper (remember
div
is an object of typeElement
but it's not already rendered in the page DOM). You can use all of Element's methods on it, including.innerHTML
:div.innerHTML = ` <div class="item"> ... </div> `;
then append the wrapper's first child to the parent element:
divParent.appendChild(div);
you can use
.querySelectorAll
instead of.getELementByClassName
. The former allows you to pass in an actual CSS selector instead of a className (the latter method). In your case you only want the first matching element, so use.querySelector
instead.I always use single quotes
'
for JavaScript strings and double quotes"
when I need quotes inside the strings e.g. with HTML attributes. That's personal preference though.no need to define the variable
docData
asdoc.data()
is already pretty self-explanatoryyou can use arrow functions outside also
(arg1, arg2, ...) => {}
or if you have a single argumentarg => {}
(no need for the parenthesis).you could use
const
for constant variable to make sure they don't get their values changed: for instanceitemsDiv
(fyi: renamed toitemsParent
for clarity) can be defined withconst
instead ofvar
.
Here's a possible refactoring of your code:
const itemsParent = document.querySelector('.items')[0];
let items = [];
const renderItems = items => {
items.forEach(item => {
const div = document.createElement('div');
div.innerHTML = `
<div class="item">
<img class="itemImgBackground" src="assets/${item.name.replace(" ", "")}.png">
<img class="itemImg" src="assets/${item.name.replace(" ", "")}.png">
<span class="itemName">${item.name}</span>
<span class="itemCondition">${item.condition}</span>
<span class="itemPrice">${item.price}</span>
</div>
`.trim();
itemsParent.appendChild(div.firstChild);
});
}
db.collection('items').get().then((querySnapshot) => {
querySnapshot.forEach(doc => {
items.push(doc.data());
});
renderItems(items);
});
Edit: testing div creation, it appears you need to trim the string first:
const div = document.createElement('div');
div.innerHTML = `
<div class="parent">
<div class="child"></div>
</div>
`.trim();
console.log(div.outerHTML)
console.log(div.firstChild.outerHTML)
-
\$\begingroup\$ The code doesn't work, it happens when you append
div.firstChild
\$\endgroup\$Jordan Baron– Jordan Baron2018年06月29日 16:46:41 +00:00Commented Jun 29, 2018 at 16:46 -
\$\begingroup\$ For some reason,
div.firstChild
returns an object. Removing<div class="item"
and addingdiv.className = 'item'
after creating the div fixes the problem. \$\endgroup\$Jordan Baron– Jordan Baron2018年06月29日 16:49:13 +00:00Commented Jun 29, 2018 at 16:49 -
\$\begingroup\$ @JordanBaron I have corrected my answer: you first need to trim the string otherwise it won't work. Try again my code snippet to see if it works. \$\endgroup\$Ivan– Ivan2018年06月29日 17:07:42 +00:00Commented Jun 29, 2018 at 17:07
-
\$\begingroup\$ "you can use
.querySelectorAll
instead of.getELementByClassName
" but the latter is more than twice as fast... \$\endgroup\$2019年08月14日 23:23:45 +00:00Commented Aug 14, 2019 at 23:23
Similar to what I mentioned in my review of your other Post: Carousel in Vanilla JavaScript, if there is only one element that has class items then it would be more appropriate to use an id attribute and query the DOM using document.getElementById()
.
As Ivan mentioned, when defining an arrow functions: "Parentheses are optional when there's only one parameter name"1
so instead of:
querySnapshot.forEach((doc) => {
The parentheses around doc
can be removed:
querySnapshot.forEach(doc => {
The callback function that takes each element from the querySnapshot
pushes the result from calling .data()
into items
.
querySnapshot.forEach((doc) => { var docData = doc.data(); items.push(docData); });
This can be simplified using the Array.map()
method:
items = querySnapshot.map(doc => doc.data());
If you do that, you could declare items
as a const
and pass it as an argument to renderItems()
instead of declaring it at the top of the script.
Explore related questions
See similar questions with these tags.