How to iterate through an array inside an array.map function? - javascript

I am trying to iterate an array inside another loop in my react app. I have a json file that contains data that looks like this:
[
{
"id":"0001",
"photos":[
"IMG_9239.JPG",
"2019-01-07.jpg",
"IMG_9261.JPG"
]
},
{
"id":"0002",
"photos":[
"IMG_9239.JPG",
"2019-01-07.jpg",
"IMG_9261.JPG"
]
},
{
"id":"0003",
"photos":[
"IMG_9239.JPG",
"2019-01-07.jpg",
"IMG_9261.JPG"
]
}
]
And this is my react component:
const ListItems = ({data}) => {
return (
<div id="items-container">
{data.map( item => (
<p>{item.id}</p>
//iterate the [photos] array in an img tag
//<img src ="photo" />
))}
</div>
)
}
I am trying {item.photos.map....} but it seems like it's not a valid syntax. Can you please help?

Multiple JSX elements require a parent, or at least a psuedo-parent. Here, you can use a fragment <> as the parent of what gets returned from the .map callback, enclosing both the <p> and the <img>s:
const ListItems = ({data}) => {
return (
<div id="items-container">
{data.map( item => (
<>
<p>{item.id}</p>
{item.photos.map(({ src }) => <img src={src}></img>}
</>
))}
</div>
)
}

Related

Transform array of objects having HTML within its properties into JSX-elements

so like in my question, I have an issue with creating an element.
{collection.map((element,index ) => {
return <div class='food-carousel' key={index} >{element}</div>;
})}
the collection is an array that holds two images and titles. So each literal should give
<div class='food-carousel'> <img ..> <img ..> <h1>some</h1><div>
but for now, I got
react-dom.development.js:55 Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {html}). If you meant to render a collection of children, use an array instead.
also, I would like to add a condition if index=2 div class='food-carousel active-element'>
Updated:
.map(({html}, index ) => {
return <div key={index} >{html}</div>;
})}
That mapping fix an error but instead of elements I got pure HTML
I assume, you pass an object having HTML source within its properties as {element}. So, just like the error reads, you cannot do that and should go something, like:
{
collection.map((element,index ) => (
<div
className={`food-carousel${index==2 ? ' active-element' : ''}`}
key={index}
>
{Object.values(element)}
</div>
))
}
Above will work if object properties have JSX as values, if it's escaped string, you would need to use dangerouslySetInnerHTML as #JosephD suggested.
Following is the live-demo of both approaches:
const { render } = ReactDOM
const collection1 = [
{html: <span>That is my first item</span>},
{html: <span>That is my second one</span>},
{html: <span>And here goes the third</span>},
],
collection2 = [
{html: '<span>That is my first item</span>'},
{html: '<span>That is my second one</span>'},
{html: '<span>And here goes the third</span>'},
]
const Test1 = () => (
<div>
{
collection1.map((element,index ) => (
<div
className={`food-carousel${index==2 ? ' active-element' : ''}`}
key={index}
>
{Object.values(element)}
</div>
))
}
</div>
)
const Test2 = () => (
<div>
{
collection2.map((element,index ) => (
<div
className={`food-carousel${index==2 ? ' active-element' : ''}`}
key={index}
dangerouslySetInnerHTML={{__html:element.html}}
/>
))
}
</div>
)
render (
(
<div>
<Test1 />
<hr />
<Test2 />
</div>
),
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
However, it is important to note, that throwing in HTML-markup is not a good practice at all and you should consider building up JSX elements from element properties data instead.

ES6 map function for tuples/2d arrays

I'm trying to write a map function to return a navbar.
I have an array/dictionary that I want to loop through using the map function that looks something like
[['Text', './link.html'],
['Text2', './link2.html'],
['Text3', './link3.html']]
or else
{'Text', './link.html',
'Text2', './link2.html',
'Text3', './link3.html'}
Whatever type I need to loop through doesn't matter, I just want to loop through sets of 2, ideally I'd want to use tuples, but it doesn't look like that's an option from what I've read.
When looking at solutions that use the dict/object method, I don't know how to access both the key and the value. For ex.
var NavBar = (props) => {
return (
<div id="NavMain">
{Object.keys(props.links).map((link,index) => {
return <NavBarItem link={link} />
{/* Where do I access the key? */}
})}
</div>
)}
If I try to map this as a 2d array, my IDE is showing some error lines underneath 'row', 'text' and '/>' in the code below
var NavBar = () => {
return (
<div id="NavMain">
{this.props.links.map((row,index) => {
return <NavBarItem link=row[1] text=row[0] />
})}
</div>
)}
Other solutions I've looked up are really messy. I'm wondering if there's a clean way to use the map function over sets of 2.
You can use array destructuring inside the .map() like this:
So assuming you have a data set of an array of arrays:
const arr = [
['Text', './link.html'],
['Text2', './link2.html'],
['Text3', './link3.html']
]
var NavBar = (props) => {
return(
<div id="NavMain">
{arr.map(([text, link]) => {
return <NavbarItem link={link} text={text}/>
})}
</div>
)
}
We know the first item is text and the second item is link as expected.
See sandbox for working example: https://codesandbox.io/s/stoic-payne-9zdp3
You almost had it. Try this:
/*
props.links = {
'Text' : './link.html',
'Text2' : './link2.html',
'Text3' : './link3.html'
}
*/
var NavBar = (props) => {
return (
<div id="NavMain">
{Object.keys(props.links).map((key, index) => {
return <NavBarItem link={props.links[key]} text={key} />
})}
</div>
)
}

Curly braces inside curly braces in React

I have presentational component in React. And with products.some i am trying to check if any item inside products is checked. And if some item is checked, render parent block for RequestedProduct component. I know that the problem is a second pair of curly braces as React think it's a prop. Is there another way to do this?
const Requested = ({ products, getCurrentTime }) => (
<div className="pepper-pin-body-tab requested-tab">
<div className="pepper-pin-body-tab-title">
Запрошенные
</div>
<div className="pepper-pin-body-tab-indicator" />
{products.some(product => product.checked) ? (
<div className="requested-tab-list-requested">
<div className="requested-tab-list-requested-time">
{getCurrentTime()}
</div>
{products.filter((product, key) => {
if (product.checked) {
return (
<RequestedProduct
key={key}
title={product.title}
/>
);
}
})}
</div>
) : null}
</div>
);
Issue is, filter will not return the custom element/value, it will always return the array element for which you return true from filter body.
Solution is, use only map or combination of filter and map.
Using map:
{
products.map((product, key) => product.checked ?
<RequestedProduct key={key} title={product.title} />
: null
}
Using combination of filter and map:
{
products
.filter(product => product.checked)
.map((product, key) => <RequestedProduct key={key} title={product.title}/>)
}
Check this snippet, you will get a better idea:
const arr = [
{a: 1},
{a: 2},
{a: 3},
{a: 4}
];
const afterFilter = arr.filter((el,i) => {
if(i%2) {
return `Hello world ${i}`;
}
});
// it will print the array items, not the Hello World string
console.log('afterFilter', afterFilter);
I'd recomment splitting the code a bit, which makes it intent a lot clearer. You'll end up with the following (for example), which should not be triggering errors.
The main problem is in the unintended side effects of the filter, whereas you most likely want to use a filter and a map. That makes the intent to another developer much clearer.
const contents = (products, getCurrentTime) => (
const filtered = products.filter(product => product.checked);
<div className="requested-tab-list-requested">
<div className="requested-tab-list-requested-time">
{getCurrentTime()}
</div>
{filtered.map((product, key) => <RequestedProduct key={key} title={product.title}/>)}
</div>
);
const Requested = ({products, getCurrentTime}) => {
const isAnythingChecked = products.some(product => product.checked);
return <div className="pepper-pin-body-tab requested-tab">
<div className="pepper-pin-body-tab-title">
Запрошенные
</div>
<div className="pepper-pin-body-tab-indicator"/>
{isAnythingChecked ? contents(products, getCurrentTime) : null}
</div>
};

React - how to map nested object values?

I am just trying to map nested values inside of a state object. The data structure looks like so:
I want to map each milestone name and then all tasks inside of that milestone. Right now I am trying to do so with nested map functions but I am not sure if I can do this.
The render method looks like so:
render() {
return(
<div>
{Object.keys(this.state.dataGoal).map( key => {
return <div key={key}>>
<header className="header">
<h1>{this.state.dataGoal[key].name}</h1>
</header>
<Wave />
<main className="content">
<p>{this.state.dataGoal[key].description}</p>
{Object.keys(this.state.dataGoal[key].milestones).map( (milestone, innerIndex) => {
return <div key={milestone}>
{milestone}
<p>Index: {innerIndex}</p>
</div>
})}
</main>
</div>
})}
</div>
);
}
I think that I could somehow achieve that result by passing the inner index to this line of code: {Object.keys(this.state.dataGoal[key].milestones) so it would look like: {Object.keys(this.state.dataGoal[key].milestones[innerIndex]).
But I am not sure how to pass the innerIndex up. I have also tried to get the milestone name by {milestone.name} but that doesn't work either. I guess that's because I have to specify the key.
Does anybody have an idea? Or should I map the whole object in a totally different way?
Glad for any help,
Jakub
You can use nested maps to map over the milestones and then the tasks array:
render() {
return (
<div>
{Object.keys(this.state.dataGoal.milestones).map((milestone) => {
return (
<div>
{this.state.dataGoal.milestones[milestone].tasks.map((task, idx) => {
return (
//whatever you wish to do with the task item
)
})}
</div>
)
})}
</div>
)
}
What you want is flatMap. flatMap takes an array and a function that will be applied to each element in the array, which you can use to (for example) access properties inside each object in the array. It then returns a new array with the returned values from its lambda:
function flatMap(arr, lambda) {
return Array.prototype.concat.apply([], arr.map(lambda))
}
In our case, we don't have an array, we have an object so we can't use flatMap directly. We can convert the object to an array of its properties' values with Object.values and then make a function that accesses the object with the passed key:
function tasksFromDataGoal(key) {
return flatMap(Object.values(dataGoal[key].milestones), milestone => milestone.tasks)
}
Working example:
function flatMap(arr, lambda) {
return Array.prototype.concat.apply([], arr.map(lambda))
}
function tasksFromDataGoal(key) {
return flatMap(Object.values(dataGoal[key].milestones), milestone => milestone.tasks)
}
const dataGoal = { 123: { milestones: { milestone1: { tasks: ['a', 'b'] }, milestone2: { tasks: ['c', 'd'] } } } }
alert(tasksFromDataGoal('123'))
Author of this implementation of flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
Managed to refactor the render method:
render() {
return(
<div>
{Object.keys(this.state.dataGoal).map( (key, index) => {
const newDataGoal = this.state.dataGoal[key].milestones;
return <div key={key}>
<header className="header">
<h1>{this.state.dataGoal[key].name}</h1>
</header>
<Wave />
<main className="content">
<p>{this.state.dataGoal[key].description}</p><br /><br />
{Object.keys(this.state.dataGoal[key].milestones).map( (milestoneKey) => {
const milestonesData = this.state.dataGoal[key].milestones[milestoneKey];
return <div className="milestone-wrap" key={milestoneKey}>
<label className="milestone-label">{milestonesData.name}</label>
{Object.keys(milestonesData.tasks).map( (taskKey) => {
return <div className="task clearfix" key={taskKey}>
<input
className="checkbox-rounded"
name="task"
type="checkbox"
checked={milestonesData.tasks[taskKey].done}
onChange={(e) => this.handleInputChange(e, key, taskKey)} />
<div className="task-content">
<p className="task-name">{milestonesData.tasks[taskKey].name}</p>
<p className="task-date">{milestonesData.tasks[taskKey].finishDate}</p>
</div>
</div>
})}
</div>
})}
</main>
</div>
})}
</div>
);
}

How do I loop through an array in an array of objects

I know how to run loops inside react but how do I do it inside an object which is already inside an array being looped?
I am trying to display each ingredient item as an <li>, so far I have got it working with recipe but I am lost with ingredient. If anyone could chime in, I'd appreciate it.
var Recipes = React.createClass({
// hook up data model
getInitialState: function() {
return {
recipeList: [
{recipe: 'Cookies', ingredients: ['Flour ', 'Chocolate']},
{recipe: 'Cake', ingredients: ['Flour ', 'Sprinkles']},
{recipe: 'Onion Pie', ingredients: ['Onion ', 'Pie-Crust']}
]
}
},
loop: function() {
{this.state.recipeList.flatMap('ingredients').map(item, index) => (
<li key={index} className="list-group-item">{ingredient.ingredients}</li>
)}
},
render: function() {
return (
<div>
{this.state.recipeList.map((item, index) => (
<div className="panel panel-default">
<div className="panel-heading"><h3 className="panel-title">{item.recipe}</h3></div>
<div className="panel-body">
<ul className="list-group">
{this.loop}
</ul>
</div>
</div>
)
)}
</div>
);
}
});
How about this way :
loop: function(ingredients) {
return ingredients.map((ingredient, index) => {
return (<li key={index} className="list-group-item">{ingredient}</li>)
})
},
render(){
...
{this.loop(item.ingredients)}
...
}
One more thing, you shouldn't use index of array as key because it will be difficult to manage when editting the array later. It will be better if you assign key with something very unique like id or index + Date.now()
You seem to be missing a return statement in the loop method.
You can cascade rendering as deep as you'd wish, only remember that you need to call the method instead of just placing it in the component structure (see this.loop without call parentheses in your sample):
var myComponent = React.createClass({
renderListElements: function (parent) {
return this.state.listElements[parent].map((element, index) => (
<li
className="my-component__sub-component__list-element"
key={`${parent.uid}_${element.uid}`}
>
{element.name}
</li>
));
},
render: function () {
var parentsId = [ 0, 1, 2, 3 ];
return (
<div className="my-component">
{parentsId.map((item, index) => (
<div
className="my-component__sub-component"
key={item.uid}
>
{this.renderListElements(item)}
</div>
)}
<div/>
);
}
});

Categories

Resources