Calling a function inside forEach - javascript

//Array of books
const books = [{
title: 'The subtle art of not giving a fuck',
author: 'Mark Manson'
},{
title: 'Everything is fucked',
author: 'Mark Manson'
},{
title: 'Seriousness takes the joyfulness of life',
author: 'Karan Siddannavar'
}]
const getBooks = book => {
document.querySelector('.book-list').innerHTML = `<li>${book.title}</li>
<li>${book.author}</li>`
}
books.forEach(book => {
getBooks(book);
})
<div class="book-list"></div>
I call the getBooks method every time the book variable is updated with a new object. But, the function gets called only once. In other words, the details of only one book is displayed, even though the function is called every time the forEach loops through the array. Why does this happen?

In each call of the function you are replacing the innerHTML (previous content with the current content), you have to use += instead of = to retain the previous HTML.
Though, I will suggest you to use Element.insertAdjacentHTML() instead of innerHTML:
const books = [{
title: 'The subtle art of not giving a fuck',
author: 'Mark Manson'
},{
title: 'Everything is fucked',
author: 'Mark Manson'
},{
title: 'Seriousness takes the joyfulness of life',
author: 'Karan Siddannavar'
}]
const getBooks = book => {
document.querySelector('.book-list').insertAdjacentHTML('beforeend', `<li>${book.title}</li>
<li>${book.author}</li>`)
}
books.forEach(book => {
getBooks(book);
})
<div class="book-list"></div>

You could use map method instead of forEach and first create content then add it to DOM so you don't have to access DOM in each iteration.
const books = [{
title: 'The subtle art of not giving a fuck',
author: 'Mark Manson'
}, {
title: 'Everything is fucked',
author: 'Mark Manson'
}, {
title: 'Seriousness takes the joyfulness of life',
author: 'Karan Siddannavar'
}]
const getBooks = ({title, author}) => `<li>${title}</li><li>${author}</li>`
const content = books.map(getBooks).join('');
document.querySelector('.book-list').innerHTML = content;
<div class="book-list"></div>

Related

Sorting based on the category level

Just started diving into the array of objects sorting in JavaScript, having come up with the following snippet. Is there a chance this code can somehow be optimised for performance or does this look generally legit?
const products = [
{
title: "Test product1",
description: "Test description",
price: 5,
category: {
title: 'hot_dishes',
priority: 2
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
},
{
title: "Test product2",
description: "Test description",
price: 5,
category: {
title: 'dessert',
priority: 1
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}
];
const sorted = products
.map(({ category }) => category)
.sort((a, b) => parseFloat(a.priority) - parseFloat(b.priority))
.map(({ title }) => (
products.filter(({ category: { title: cTitle } }) => title === cTitle)
));
console.log(sorted);
Workflow:
Destructure category from each product
Sort the in ascending order
Filter in separate arrays, based on the category title
My current biggest concern is about the following line:
.map(({ title }) => (
products.filter(({ category: { title: cTitle } }) => title === cTitle)
));
You are indirectly sorting the array using priority, But you don't have to go through all the steps. You can just use sort on the products also
const sorted = products.sort((a, b) => a.category.priority - b.category.priority);
const products = [
{
title: "Test product1",
description: "Test description",
price: 5,
category: {
title: "hot_dishes",
priority: 2
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}, {
title: "Test product2",
description: "Test description",
price: 5,
category: {
title: "dessert",
priority: 1
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}
];
const sorted = products.sort((a, b) => a.category.priority - b.category.priority);
console.log(sorted);
/* This is not a part of answer. It is just to give the output full height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can optimize this further, and there are some edge cases to watch out for based on the shape of your input.
On thing you could do overall is to normalize categories into a separate collection and reference the category id in the product. This would remove the possibility of having the same category title with different priorities in different objects.
You also haven't handled the sorting behavior of multiple categories with the same priority in a way that guarantees sort order, you should sort by priority and alpha by title to fix that. This sort call relies on the browser's implementation of sort() and therefore should be well optimized.
Your algorithm maps over product and filters product for each iteration of the map, which is O(n^2) time complexity. Making this a O(n) algorithm will save you a ton of time on a big dataset.
I've added a snippet that de-duplicates the categories using a Map before sorting, and caches the category array index before grouping using a Map there too. Those optimizations are admittedly pretty small compared to the overall improvement of finding a single pass algorithm for grouping your products.
An empty 2 dimensional array is created from the deduped and sorted category array. Then we iterate over the products array and add the products to the proper category array.
We've made the sort a little faster and more accurate; everything else is now accomplished in amortized constant time.
const products = [{
title: "Test product1",
description: "Test description",
price: 5,
category: {
title: 'hot_dishes',
priority: 2
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
},
{
title: "Test product2",
description: "Test description",
price: 5,
category: {
title: 'dessert',
priority: 1
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}
];
const categoryComparator = (a, b) => {
const priorityComparison = parseFloat(a.priority) - parseFloat(b.priority)
if ( priorityComparison !== 0 ) return priorityComparison
return a.title.localeCompare(b)
}
const categoryMap = new Map()
products.forEach(product =>
categoryMap.set(product.category.title, product.category)
)
const sortedCategories = Array.from(categoryMap, ([title, category]) => category)
.sort(categoryComparator)
.map(category => category.title)
const categoryIndexMap = new Map([...new Set(sortedCategories)].map((category, index) => [category, index]))
const categorizedProductArrays = Array.from({
length: categoryIndexMap.size
}, i => [])
products.forEach((product) =>
categorizedProductArrays[categoryIndexMap.get(product.category.title)].push(product))
console.log(categorizedProductArrays)

Node.js - Express routing endpoint to Object Key Value inside array

I have a local server that I have created to learn and practice my backend coding. Right now its in the early stages of becoming a "netflix" style app. I have a code:
app.get("/movies/:title", (req, res) => {
res.json(movies.find((movie) => {
return movie.title === req.params.title
}));
});
that when I type this URL: localhost:8080/movies/:title (insert title name) it returns the desired movie form this array:
let movies = [
//1
{
title: 'Lord of the Rings',
actor: 'Orlando',
genre: 'adventure',
director: 'person'
} ,
//2
{
title: 'Harry Potter',
actor: 'Daniel Radcliffe',
genre: 'Fantasy',
director: 'person',
Movie_ID: "7"
} ,
//3
{
title: 'Imaginaerum',
actor: 'Toumas Holopainen',
genre: 'Fiction',
director: 'person',
Movie_ID: "1"
} ,
//4
{
title: 'Cloud Atlas',
actor: 'Person',
genre: 'Fantasy',
director: 'person'
}
However, when I try to do the same, but with the key value "actor" in this URL:
localhost:8080/movies/:actor (replace for actor name)
nothing shows up. Here is the code for that:
app.get("/movies/:actor", (req, res) => {
console.log(req.params)
res.json(movies.find(movie => {
return movie.actor === req.params.actor
}));
});
All help is greatly appreciated!
As #Đăng Khoa Đinh explained, these are the same routes, so your code doesn't know which end point to use.
Change one to:
/movies/actor/:actor/ and the other to /movies/title/:title or a similar change to get this working.

Structuring Nested Arrays

I have a fetch api call which returns a really large array full of books and associated information, from which I only need the title, author, and associated passages. In componentDidMount() I am trying to grab the these values, and push them into a new array, structured in a way that makes sense for my project.
I am successfully grabbing and pushing the values, but I'm not able to maintain the nested structure. I would like the book title and author at the top, and then have the passages in a nested array associated with the book. My current code just pushes everything to same level, with no nesting.
In the code below, I would essentially like this.state.dummyBooks and this.state.booksStructured to be identical.
jsfiddle
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dummyBooks: [
{
title: "book title one",
author: "book author one",
passages: [
{
id: 1,
passage: "this is a passage",
},
{
id: 2,
passage: "this is another passage",
},
],
},
{
title: "book title two",
author: "book author two",
passages: [
{
id: 3,
passage: "this is a third passage",
},
{
id: 4,
passage: "this is yet another passage",
},
],
},
],
booksStructured: [],
};
}
componentDidMount() {
this.state.dummyBooks.map(bookObject => {
this.state.booksStructured.push({
bookTitle: bookObject.title,
bookAuthor: bookObject.author,
});
bookObject.passages.map(passageObject => {
this.state.booksStructured.push({
passageID: passageObject.id,
passageText: passageObject.passage,
});
});
});
}
render() {
console.log(this.state.booksStructured);
return <div>check the console</div>;
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
Just grab the required keys from this.state.dummyBooks and push it to the this.state.booksStructured:
componentDidMount() {
for(const {title, author, passages} of this.state.dummyBooks){
this.state.booksStructured.push({title:title,author:author,passages:passages});
}
}
Rather than trying to set this.state.booksStructured as your looping through this.state.dummyBooks, create an array based on the mapping values in this.state.dummyBooks, creating variables and assigning the updated key/value pairs accordingly. When that's finished use this.setState({...}) to assign the new array to this.state.booksStructured.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
dummyBooks: [
{
'title': 'book title one',
'author': 'book author one',
'passages': [
{
'id': 1,
'passage': 'this is a passage'
}, {
'id': 2,
'passage': 'this is another passage'
}
]
}, {
'title': 'book title two',
'author': 'book author two',
'passages': [
{
'id': 3,
'passage': 'this is a thridpassage'
}, {
'id': 4,
'passage': 'this is yet another passage'
}
]
}
],
booksStructured: [],
}
}
componentDidMount() {
var booksStructured = this.state.dummyBooks.map(bookObject => {
var obj = {}
var {title, author, passages} = bookObject
obj['bookTitle'] = title
obj['bookAuthor'] = author
passages = passages.map(x=>{
var passageObj = {}
passageObj['passageID'] = x.id
passageObj['passageText'] = x.passage
return passageObj
});
obj['passages'] = passages
return obj
})
this.setState({
booksStructured
})
}
render() {
console.log(this.state.booksStructured);
return (
<div>
check the console
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
Check it out here.
If you look closely at the code in your componentDidMount method, you should notice that what you're actually doing is iterating over dummyBooks and pushing an object containing only the book author and title into the booksStructured array before pushing an object containing only the passage id and text into the booksStructured array. Hence, the resulting array of alternating book then passage objects.
Instead, you would want to create one object containing both book and passage information before pushing it into the booksStructured array. You should also not that it's advised to use setState() in favor of this.state.push():
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable. docs
and you should generally not call setState() in a loop because
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. docs
With that in mind, your solution should look something like this:
componentDidMount() {
const structuredBooks = [];
this.state.dummyBooks.map(bookObject => {
// Iterate over each passage, returning a new object with only
// the wanted fields from each passage object
const structuredPasssages = bookObject.passages.map(passage => {
passageID: passageObject.id,
passageText: passageObject.passage,
});
// Construct a structured book object using the new passages
const structuredBook = {
bookTitle: bookObject.title,
bookAuthor: bookObject.author,
passages: structuredPassages,
};
// Push the structured book into "structuredBooks"
structuredBooks.push(structuredBook);
});
// Update state with the complete structuredBooks array
this.setState({ booksStructured: structuredBooks });
}
Use map to loop through the object and return the structure you want.
const booksStructured = apiData.map(book => {
return {
title: book.title,
author: book.author,
passages: book.passages
};
});
Please don't modify state variables directly, like
this.state.booksStructured.push({ title:title,author:author, passages:passages});
Save the value in a local variable and update state using .setState()
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script>
const apiData = [
{
title: 'book title one',
author: 'book author one',
more: 'more',
xtra: 'tets',
year: 2030,
passages: [
{
id: 1,
passage: 'this is a passage',
},
{
id: 2,
passage: 'this is another passage',
},
],
},
{
title: 'book title two',
author: 'book author two',
more: 'more',
xtra: 'tets',
year: 2000,
passages: [
{
id: 1,
passage: 'this is a passage',
},
{
id: 2,
passage: 'this is another passage',
},
],
},
{
title: 'book title three',
author: 'book author three',
more: 'more',
xtra: 'tets',
year: 2001,
passages: [
{
id: 1,
passage: 'this is a passage',
},
{
id: 2,
passage: 'this is another passage',
},
],
},
];
</script>
<script type="text/babel">
class App extends React.Component {
constructor() {
super();
this.state = {
name: "React",
dummyBooks: [
{
title: "book title one",
author: "book author one",
passages: [
{
id: 1,
passage: "this is a passage"
},
{
id: 2,
passage: "this is another passage"
}
]
},
{
title: "book title two",
author: "book author two",
passages: [
{
id: 3,
passage: "this is a third passage"
},
{
id: 4,
passage: "this is yet another passage"
}
]
}
],
booksStructured: []
};
}
componentDidMount = () => {
const booksStructured = apiData.map(book => {
return {
title: book.title,
author: book.author,
passages: book.passages
};
});
this.setState({
booksStructured: booksStructured
});
};
render() {
console.log(this.state.booksStructured);
return <div>check the console</div>;
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>

extract properties from array of objects and store in another array of objects

i have an array of objects that is coming from server.
data:[
{
// values
}
]
below is one object element of that array.
0:
assignedTo:{
name: "Shokat iqbal"
},
category:{
name: "Fan"
},
complainer:{
name: "Testt"
},
details: "Makk Amjum and my name is yeh and i amthose who is ur father"
location: "Room number 87 blockasdas jknaksdnkaj knasdkan kasndka nasdkas"
status: "in-progress"
title: "My fan is damaged"
_id: "5cade948e0b7ce30c8ef2f05"
i want to extract some of its properties like
Assignee: assignedTo.name, category: Category.name, Complainer:complainer.name
and want to make another array of objects which will look like this.
[
{
Assignee: assignedTo.name,
Complainer: complainer.name,
Category: category.name,
title: title,
location: location,
details: details
}
]
please help me how to do this?
Welcome to SO. It's always good to try and show, or talk about some of the solutions that you have tried and where they failed. The community is great but they also appreciate people trying.
Array.map will do the trick for you
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const formattedData = data.map(record => ({
Assignee: record.assignedTo.name,
Complainer: record.complainer.name,
Category: record.category.name,
title: record.title,
location: record.location,
details: record.details,
}));

Why JavaScript deconstructing not working inline

In hash color function, I assign title and rating by deconstructing them from another object but instead it assign the full color object, and if I break the code into two lines (first deconstructing and assign(reconstructing) then it works fine. Please explain the logic running behind this.
const colors = [{
id: '-xekare',
title: "rad red",
rating: 3
}, {
id: '-jbwsof',
title: "big blue",
rating: 2
}, {
id: '-prigbj',
title: "grizzly grey",
rating: 5
}, {
id: '-ryhbhsl',
title: "banana",
rating: 1
}]
hashcolor=colors.reduce((hash,color)=>{
hash[color.id]={title,rating}=color
return hash
},[])
console.log(hashcolor);
When you're destructuring, you're creating (or assigning to) individual variables - you're not creating a new object, so hash[color.id] = { title, rating } doesn't actually work. You could write (or copy) a pick function that accomplishes that, but in this case, you could just destructure in the arguments instead.
You should also probably use an object as the accumulator, rather than an array:
const colors = [{
id: '-xekare',
title: "rad red",
rating: 3
}, {
id: '-jbwsof',
title: "big blue",
rating: 2
}, {
id: '-prigbj',
title: "grizzly grey",
rating: 5
}, {
id: '-ryhbhsl',
title: "banana",
rating: 1
}]
const hashcolor = colors.reduce((hash, { id, title, rating }) => {
hash[id] = { title, rating };
return hash;
}, {})
console.log(hashcolor);
Although it looks the same, there are actually 2 phases to a creation of an object from destructured properties:
Destructure and assign the properties to variables (or consts) - const { title, rating } = color.
Use shorthand property names to create the object const obj = { title, rating }.
In your case you can move the destructuring phase to the function call:
const colors = [{"id":"-xekare","title":"rad red","rating":3},{"id":"-jbwsof","title":"big blue","rating":2},{"id":"-prigbj","title":"grizzly grey","rating":5},{"id":"-ryhbhsl","title":"banana","rating":1}]
const hashcolor = colors.reduce((hash, { id, title, rating }) => {
hash[id] = { title, rating }
return hash
}, {})
console.log(hashcolor);
And you can also use object rest, to create the object:
const colors = [{"id":"-xekare","title":"rad red","rating":3},{"id":"-jbwsof","title":"big blue","rating":2},{"id":"-prigbj","title":"grizzly grey","rating":5},{"id":"-ryhbhsl","title":"banana","rating":1}]
const hashcolor = colors.reduce((hash, { id, ...newColor }) => {
hash[id] = newColor
return hash
}, {})
console.log(hashcolor);

Categories

Resources