React - how to map nested object values? - javascript

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>
);
}

Related

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

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>
)
}

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: Better way of mapping this data

So this is a continuation of previous questions. I just started using react and I've managed to fetch a xml file, convert it to json and then loop through the data. But I think there should be a better way to go through what I've done as I'm using a function I found on another SO answer and then used a map() within it.
Anyways here's the breakdown of my code.
Fetching the xml and converting to json:
componentDidMount() {
axios.get('https://gist.githubusercontent.com/eMatsiyana/e315b60a2930bb79e869b37a6ddf8ef1/raw/10c057b39a4dccbe39d3151be78c686dcd1101aa/guestlist.xml')
.then(res => {
const xml = XMLMapping.load(res.data);
var guests = XMLMapping.tojson(xml);
this.setState({guests: guests});
});
}
The results of the json in console:
Object{
dataset{
record{
0{
company{
$t: "Skippad"
}
first_name{
$t: "Keith"
}
last_name{
$t: "Cook"
}
}
1{
company{
$t: "Skippad"
}
first_name{
$t: "Keith"
}
last_name{
$t: "Cook"
}
}
}
}
}
I'm using this function to map the object and then using a map() within it:
function mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
});
}
This is what the final mapping of the data looks like:
{mapObject(this.state.guests, (key, value) => {
return <div key={key}>
{value.record
.filter(
(item,index) => {
return (
item.first_name.$t.toLowerCase().includes(this.state.search.toLowerCase())
//item.last_name.$t.toLowerCase().includes(this.state.search.toLowerCase())
//item.company.$t.toLowerCase().includes(this.state.search.toLowerCase())
)
}
)
.map((item,index) => {
return <div className="columns is-mobile" key={index}>
<div className="column" key={index}>{item.first_name.$t} {item.last_name.$t} <span class="is-hidden-tablet"><br />{item.company.$t}</span></div>
<div className="column is-hidden-mobile" >{item.company.$t}</div>
<div className="column is-hidden-mobile">
<EmailFormdisplay guestid={index} />
</div>
<div className="column is-hidden-mobile">
<PhoneFormdisplay guestid={index} />
</div>
<div className="column is-hidden-tablet is-one-third-mobile">
<Dropdown>
<DropdownTrigger></DropdownTrigger>
<DropdownContent>
<div className="columns">
<div className="column">
<EmailFormdisplay guestid={index} />
</div>
<div className="column">
<PhoneFormdisplay guestid={index} />
</div>
</div>
</DropdownContent>
</Dropdown>
</div>
</div>;
})}
</div>
})}
Is there a better way of doing this without using mapObject() and a map() within it?
Any kind of feedback or advice would be greatly appreciated!

How to return data in a react .map statement?

I want to use warningItem within my return statement in order to map some data into a react component.
I want to loop over area but have problems with syntax.
createWarnings = warningsRawData => {
return warningsRawData.map(warningItem => {
return (
<div>
<p className={styles.warningMainText} />
<p>warningItem.area[0]</p>
</div>
);
});
};
It looks like you are missing the brackets around it. Try:
createWarnings = warningsRawData => {
return warningsRawData.map( (warningItem, i) => {
return (
<div key={i}>
<p className={styles.warningMainText} />
<p>{warningItem.area[0]}</p>
</div>
);
});
};
Whenever you loop to return elememnt in react, add key attribute is must. Else you will get warning.And add {warningItem.area[0]}
createWarnings = warningsRawData => {
let values = warningsRawData.map((warningItem,index) => {
return (
<div key={index}>
<p className={styles.warningMainText} />
<p>{warningItem.area[0]}</p>
</div>
);
});
return values
}
;

Categories

Resources