Why am i getting this error? How can i access and print the nodes when i'm selecting the <li> tags with querySelectorAll?
script.js:14 Uncaught TypeError: list.map is not a function
HTML
<ul class="wrapper1" id="testDiv">
<li class="cake">Carrots</li>
<li class="cake">Cake</li>
<li class="cake">Wheat</li>
<li class="cake">Balloons</li>
</ul>
JS
let list = document.querySelectorAll("li");
let items = list.map(elem => {
console.log(elem);
})
querySelectorAll() returns a static (not live) NodeList representing a list of the document's elements that match the specified group of selectors. Use array#from to convert NodeList to array then iterate through array#map.
let list = document.querySelectorAll("li");
let items = Array.from(list).map(elem => {
console.log(elem);
})
<ul class="wrapper1" id="testDiv">
<li class="cake">Carrots</li>
<li class="cake">Cake</li>
<li class="cake">Wheat</li>
<li class="cake">Balloons</li>
</ul>
In addition to Itang and Foo's suggestion, you can also use:
[].concat(document.querySelectorAll("li")).map((el) => { console.log(el); })
In fairness I think Foo's suggestion using spread syntax Is probably the most elegant, I'm just not sure how widely the spread operator is supported for NodeLists. (pasted below for reference)
[...document.querySelectorAll('li')].map((el) => { console.log(el); })
If you're using ES6, you can use [...selectors] syntax, like this:
let getMappingList = function (list) {
console.log(typeof list);
for (let item of list) {
console.log(item);
}
console.log("___________________");
list.map(item => console.log(item));
};
getMappingList([...document.querySelectorAll("li")]);
<ul class="wrapper1" id="testDiv">
<li class="cake">Carrots</li>
<li class="cake">Cake</li>
<li class="cake">Wheat</li>
<li class="cake">Balloons</li>
</ul>
After getting the list, we can also use map function, or looping the list using for...of... syntax.
Array(...selectors) is the same way to use:
let getMappingList = function (list) {
console.log(typeof list);
for (let item of list) {
console.log(item);
}
console.log("___________________");
list.map(item => console.log(item));
};
getMappingList(Array(...document.querySelectorAll("li")));
<ul class="wrapper1" id="testDiv">
<li class="cake">Carrots</li>
<li class="cake">Cake</li>
<li class="cake">Wheat</li>
<li class="cake">Balloons</li>
</ul>
I know this is an old thread but listed as #1 on Google search result. So here is an alternative to those in need.
let items = [].map.call(list, item => console.log(item));
I encountered the question of how to use a nodelist (result of document.querySelectorAll) with .map on my project and solved it by using a SPREAD operator to create an array from the nodelist.
Learning as I go so don't hesitate to correct me. It's weird that modern browsers can directly execute a foreach function to a nodelist but not map.
let list = document.querySelectorAll("li");
let items = [...list].map(elem => {
console.log(elem);
})
<ul class="wrapper1" id="testDiv">
<li class="cake">Carrots</li>
<li class="cake">Cake</li>
<li class="cake">Wheat</li>
<li class="cake">Balloons</li>
</ul>
edit : understood how snippets work / SPREAD not REST
Related
I want to save a set of ids based on a nodeList returned by a querySelector.
I achieved it with a very procedural approach, thinking in terms of steps.
Select items
Create an array
Iterate over items
Get the id
Push it into the array
const getThem = document.querySelectorAll('.get-me');
let arrayOfData = [];
Array.prototype.forEach.call (getThem, function (node){
arrayOfData.push(node.id);
});
console.log(arrayOfData);
<ul>
<li class="get-me" id="one">One</li>
<li class="get-me" id="two">Two</li>
<li class="get-me" id="three">Three</li>
</ul>
I wonder if I can get this done with array map, to reduce the amount of lines and also take advantage of modern Javascript features.
I tried with:
const getThem = document.querySelectorAll('.get-me');
Array.prototype.map.call(getThem, (item) => ({ [id]: item.id }));
console.log(getThem);
But it doesn't work.
Unfortunately, the NodeList that is returned by .querySelectorAll() doesn't have a .map() method. However, as it is iterable, you can pass it to Array.from(), and use the second mapping argument to perform your mapping:
const getThem = document.querySelectorAll('.get-me');
const arrayOfData = Array.from(getThem, node => node.id);
console.log(arrayOfData);
<ul>
<li class="get-me" id="one">One</li>
<li class="get-me" id="two">Two</li>
<li class="get-me" id="three">Three</li>
</ul>
This does two things in one go, it converts your NodeList to an array, and it performs the mapping based on the second argument. This is opposed to doing something like Array.from(getThem).map(...) and [...getThem].map(...) which both do two iterations over your items.
I tried with:
const getThem =
document.querySelectorAll('.get-me');
Array.prototype.map.call(getThem, (item) => ({ [id]: item.id }));
console.log(getThem);
But it doesn't work.
This does also work, you just need to store the return value in a variable a log that. .map() doesn't change the original array, it returns a new one instead:
const getThem = document.querySelectorAll('.get-me');
const res = Array.prototype.map.call(getThem, (item) => item.id);
console.log(res);
<ul>
<li class="get-me" id="one">One</li>
<li class="get-me" id="two">Two</li>
<li class="get-me" id="three">Three</li>
</ul>
Another way to do it using spread operator and array map like below
const getThem = document.querySelectorAll('.get-me');
const nodes = [...getThem];
const arrayOfData = nodes.map(node => node.id);
console.log(arrayOfData);
<ul>
<li class="get-me" id="one">One</li>
<li class="get-me" id="two">Two</li>
<li class="get-me" id="three">Three</li>
</ul>
You can do:
const elems = document.querySelectorAll('.get-me')
const arrayOfData = Array.from(elems).map(({ id }) => id)
console.log(arrayOfData)
<ul>
<li class="get-me" id="one">One</li>
<li class="get-me" id="two">Two</li>
<li class="get-me" id="three">Three</li>
</ul>
In React.js documentation, I wonder how Array.map() is used in JSX React.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
in <ul> tag, why we just put the variable listItems directly?. Because I think it will return a single array instead of <li> elements like this :
<ul>
[
<li></li>,
<li></li>,
<li></li>,
]
</ul>
how does JSX treat an array?
Did I have to loop listItems manually?
Thank you in advance.
you might want to take a look here: https://stackabuse.com/how-to-loop-in-react-jsx/ . I hope this is what you are looking for
The map() method is the most commonly used function to iterate over an array of data in JSX. You can attach the map() method to the array and pass a callback function that gets called for each iteration. When rendering the User component, pass a unique value to the key prop.
JSX treat an array like a multiple elements. You can code like this:
function NumberList() {
return (
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
);
}
Or use an array. But when use an array, we need to add an unique key attribute for each element in array:
function NumberList() {
const listItems = [
<li key="1">1</li>,
<li key="2">2</li>,
<li key="3">3</li>,
];
return (
<ul>{listItems}</ul>
);
}
If you want to using one element, you can wrap all elements to a Fragment:
function NumberList() {
const listItems = (
<>
<li>1</li>
<li>2</li>
<li>3</li>
</>
);
return (
<ul>{listItems}</ul>
);
}
Use map is same as using array:
function NumberList() {
const numbers = [1, 2, 3];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
or using map directly in JSX:
function NumberList() {
const numbers = [1, 2, 3];
return (
<ul>
{
numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
)}
</ul>
);
}
About key attribute, please refer to React's document: (https://reactjs.org/docs/lists-and-keys.html#keys)[https://reactjs.org/docs/lists-and-keys.html#keys].
Source content can be found here: https://github.com/LinkedInLearning/javascript-essential-training-2832077/tree/main/08_17. Code in question is this block right here:
import backpackObjectArray from "./components/data.js";
const content = backpackObjectArray.map((backpack)=>{
let backpackArticle = document.createElement("article");
backpackArticle.classList.add("backpack");
// Set article ID to the backpack.id property
backpackArticle.setAttribute("id", backpack.id);
backpackArticle.innerHTML=`
<figure class="backpack__image">
<img src=${backpack.image} alt="" />
</figure>
<h1 class="backpack__name">${backpack.name}</h1>
<ul class="backpack__features">
<li class="packprop backpack__volume">Volume:<span> ${
backpack.volume
}l</span></li>
<li class="packprop backpack__color">Color:<span> ${
backpack.color
}</span></li>
<li class="backpack__age">Age:<span> ${backpack.backpackAge()} days old</span></li>
<li class="packprop backpack__pockets">Number of pockets:<span> ${
backpack.pocketNum
}</span></li>
<li class="packprop backpack__strap">Left strap length:<span> ${
backpack.strapLength.left
} inches</span></li>
<li class="packprop backpack__strap">Right strap length:<span> ${
backpack.strapLength.right
} inches</span></li>
<li class="feature backpack__lid">Lid status:<span> ${
backpack.lidOpen ? "open" : "closed"
}</span></li>
</ul>
`;
return backpackArticle;
})
const main = document.querySelector(".maincontent");
content.forEach((backpack)=>{
main.append(backpack);
}
)
In essence, is it possible to just use forEach loop to output the same result as when we use map() array method, which is to output a HTML article for each object?
You can definitely use a forEach loop if you wanted to, the code will just be a little different. Using map is not necessary if you like how the new code looks.
import backpackObjectArray from "./components/data.js";
const main = document.querySelector(".maincontent");
backpackObjectArray.forEach((backpack) => {
let backpackArticle = document.createElement("article");
backpackArticle.classList.add("backpack");
// Set article ID to the backpack.id property
backpackArticle.setAttribute("id", backpack.id);
backpackArticle.innerHTML = `
<figure class="backpack__image">
<img src=${backpack.image} alt="" />
</figure>
<h1 class="backpack__name">${backpack.name}</h1>
<ul class="backpack__features">
<li class="packprop backpack__volume">Volume:<span> ${
backpack.volume
}l</span></li>
<li class="packprop backpack__color">Color:<span> ${
backpack.color
}</span></li>
<li class="backpack__age">Age:<span> ${backpack.backpackAge()} days old</span></li>
<li class="packprop backpack__pockets">Number of pockets:<span> ${
backpack.pocketNum
}</span></li>
<li class="packprop backpack__strap">Left strap length:<span> ${
backpack.strapLength.left
} inches</span></li>
<li class="packprop backpack__strap">Right strap length:<span> ${
backpack.strapLength.right
} inches</span></li>
<li class="feature backpack__lid">Lid status:<span> ${
backpack.lidOpen ? "open" : "closed"
}</span></li>
</ul>
`;
main.append(backpackArticle);
});
*Pseudo-code* Array.prototype.map(function(ea){return backpackArticle}) will simply create a new array and include whatever you return in each iteration.
You can achieve the same thing in other ways, but, as was already said, with more code. Really, the 'significance' in this case is simply achieving the intended result with less code.
I think in your example, it's to explicitly create a new array and then explicitly append each backpackArticle to the <main> using a forEach. You can skip the creating of the new array with map.
EDIT
Sorry, I havn't read your question carefully enough it seems. In your case, yes, you can interchange .map with .forEach and append the created elements directly without storing them in an intermediate array.
However, I'll leave the rest as it is, maybe someone finds it useful.
As a somewhat general answer, "yes, you could use .forEach and get the same results". And you can also do it with .reduce. However, if you want to have a list of results from a list of sources (as in your example), .map is the way to go.
I am going to answer your question in a more general way, because it
appears to me you are asking about the difference between .forEach
and .map in general.
Every method on Array.prototype is there for a purpose. The purpose of .map is to project (or "map", hence the name) a function over all items of a list. So if you want to go from a list of values to a list of other values, you can use .map.
const sources = [1, 2, 3, 4, 5];
const results = sources.map(n => n + 1);
console.log(results); // logs [2, 3, 4, 5, 6]
To get the same with .forEach, you'd have the same number of variables but two steps more you have to program: One for creating the results array, one for adding the items manually to it.
const sources = [1, 2, 3, 4, 5];
const results = [];
sources.forEach(n => {
results.push(n);
});
console.log(results);
By direct comparison you can easily see that using .forEach results slightly more code which is less declarative and more imperative in style.
As mentioned earlier, you could as well use .reduce to map a function over a value and accumulate that into a resulting list. In fact, a vast amount of operations can be written by utilizing .reduce. If you're curious, search for transducers, there are several libraries in various languages available.
But back to the example using reduce:
const sources = [1, 2, 3, 4, 5];
const results = sources.reduce((acc, n) => acc.concat(n + 1), []);
console.log(results); // logs [2, 3, 4, 5, 6]
I have a .map() function on an array.
When I do a console.log(object) inside the .map it logs the object.
But when I do <li key={key}>{object.name}</li> it shows nothing.
Also not in the inspection tool.
Component:
<StyledImagesList>
{this.state.images.map((imageObject, key) => {
// <img src={imageObject.contentUrl} title={imageObject.name} />
<li key={key}>{imageObject.name}</li>
{console.log(imageObject)}
})}
</StyledImagesList>
StyledImagesList has no styling
export const StyledImagesList = styled.ul;
Any idea why my li 's won't be visible?
Returning the <li> element in your map() callback should resolve the issue:
<StyledImagesList>
{ this.state.images.map((imageObject, key) => {
// <img src={imageObject.contentUrl} title={imageObject.name} />
/* Logging would need to happen before the return */
console.log(imageObject);
/* Add return here */
return <li key={key}>{imageObject.name}</li>
}) }
</StyledImagesList>
If logging to console were not needed, you could achieve the same result with the more concise equivalent of:
<StyledImagesList>
{ this.state.images.map((imageObject, key) => <li key={key}>{imageObject.name}</li>) }
</StyledImagesList>
Return is missing. Either
this.state.images.map((imageObject, key) => {
return <li key={key}>{imageObject.name}</li>;
})
or
this.state.images.map((imageObject, key) => (
<li key={key}>{imageObject.name}</li>
))
Map() uses return in it's code block in order to push each elements to a NEW array. It doesn't touch the original one.
The code you do inside the loop does run in fact... It's just that you didn't got any thing in return to show.
Doing
return <li key={key}>{imageObject.name}</li>
will build you a new array with those html elements for you.
The map() method creates a new array with the results of calling a function for every array element.
The map() method calls the provided function once for each element in an array, in order.
Note: map() does not execute the function for array elements without values.
Note: map() does not change the original array.
I'm building a filter for a search. Everything worked fine before since we only allowed 1 filter at a time. So I'd get a return like this:
var returnVal = "&filterName=filterProperty"
var filterFields = returnVal.split(['=']);
var filterCategory = filterFields[0];
var filterCatSplit = filterCategory.substr(1);
var filterTitle = filterFields[1];
<h4>filter title</h4>
<ul>
<li>filter one</li>
</ul>
i'd just get the returnVal and split it at the '='
Now we're going to allow multiple values and I'm not sure how to get them all onto the page in a nice list. Now, returnVal can look like this:
var returnVal = "&sizeFilterName=filterProperty,filterPropery&colorFilterName=filterProperty,filterProperty"
I now need this to return like this (or something like this)
<h4>Size Filter Name</h4>
<ul>
<li>Size Filter One</li>
<li>Size Filter Two etc</li>
</ul>
<h4>Color Filter Name</h4>
<ul>
<li>Color Filter One</li>
<li>Color Filter Two etc</li>
</ul>
I've never had to split and slice so many variants before. I'm a bit lost. I hope this is enough to get an idea of what I'm trying to do.
Thanks!
You can pass returnVal to URLSearchParams() then .split() the value by "," perform tasks using the returned array
let returnVal = "&sizeFilterName=filterProperty,filterPropery&colorFilterName=filterProperty,filterProperty";
let params = Array.from([...new URLSearchParams(returnVal)]
, ([key, value]) => [key, value.split(",").filter(Boolean)]);
// do stuff with `params`
params.forEach(([key, value]) =>
document.body.insertAdjacentHTML("beforeend", `<h4>Size ${key}</h4>
<ul>
${value.map(prop => `<li>Color ${prop}</li>`).join("")}
</ul>`)
);