Reputation:
I have an array of objects that looks like this:
const artists = [{
date: '1/1/2017',
name: 'Oasis'
},
{
date: '5/1/2017',
name: 'Blur'
},
{
date: '5/1/2017',
name: 'Simply Red'
},
{
date: '10/1/2017',
name: 'Ed Sheeran'
},
{
date: '10/1/2017',
name: 'Beyonce'
},
{
date: '15/1/2017',
name: 'Jay-z'
},
];
I want to loop over the artists
and if the date is different to the previous date I want to show that date above the artist name. The first artist should also show the date.
My code looks like this, and should hopefully make my question clearer.
render(){
{artists.map((artist, index) =>
{(artists[index].date !== artists[index - 1].date) && (
<p>{ artists[index].date }</p>
)}
<div className="artists" key={index}>
<p>{ artist.name }</p>
</div>
)}
}
Upvotes: 0
Views: 1063
Reputation: 106047
The problem is that in the first iteration of the loop, index
is 0
, so index - 1
is -1
, and obviously artists[-1]
is undefined.
A solution is to check if the current iteration is the first, which in your existing code would look like this:
(index === 0 || artist.date !== artists[index - 1].date) && (
<p>{ artist.date }</p>
)
Your code won't really work as written, however, because in some iterations of the loop you want to return one element (the artist name), and in others you want to return two (the name and date). You could make it work with map
, but it won't be very clean.
It would be cleaner with reduce
:
artists.reduce((elms, {name, date}, index) => {
if (index === 0 || date !== artists[index - 1].date) {
elms = [ ...elms, <h4 key={date}>{date}</h4> ];
}
return [ ...elms, <p key={`${date}-${name}`}>{name}</p> ];
}, [])
This code starts with an empty array ([]
) and in each iteration adds a date if it's the first iteration or the date is different from the previous, followed by the name.
However, I suggest instead pre-processing your data to be closer to the shape that you're ultimately going to render. For example:
const artists = [
{ date: '1/1/2017', name: 'Oasis' },
{ date: '5/1/2017', name: 'Blur' },
{ date: '5/1/2017', name: 'Simply Red' },
{ date: '10/1/2017', name: 'Ed Sheeran' },
{ date: '10/1/2017', name: 'Beyonce' },
{ date: '15/1/2017', name: 'Jay-Z' },
];
const artistsByDate = artists.reduce((obj, {name, date}) => {
const dateArr = obj[date] || [];
dateArr.push(name);
return { ...obj, [date]: dateArr };
}, {});
console.log(artistsByDate);
.as-console-wrapper{min-height:100%}
This code creates an object whose properties are dates and whose values are the artists associated with those dates.
Putting the data in the right "shape" before rendering it tends to make your rendering logic a lot simpler, which also makes it easier to generate a more semantic DOM structure:
function groupArtistsByDate(artists) {
return artists.reduce((obj, artist) => {
const dateArr = obj[artist.date] || [];
dateArr.push(artist);
return { ...obj, [artist.date]: dateArr };
}, {});
}
const ArtistsDateGroup = ({date, artists}) => (
<li>
<h4>{date}</h4>
<ul>
{artists.map(({name}) => <li key={name}>{name}</li>)}
</ul>
</li>
);
const Artists = ({artists}) => (
<ul>
{Object.entries(groupArtistsByDate(artists)).map(([date, dateArtists]) => (
<ArtistsDateGroup key={date} date={date} artists={dateArtists}/>
)}
</ul>
);
const artists = [
{ date: '1/1/2017', name: 'Oasis' },
{ date: '5/1/2017', name: 'Blur' },
{ date: '5/1/2017', name: 'Simply Red' },
{ date: '10/1/2017', name: 'Ed Sheeran' },
{ date: '10/1/2017', name: 'Beyonce' },
{ date: '15/1/2017', name: 'Jay-Z' },
];
ReactDOM.render(<Artists artists={artists}/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div></div>
Upvotes: 1
Reputation: 104399
Use this:
let uiItems = [], prev = '';
artists.forEach((item, i) => {
if(item.date != prev){
uiItems.push(<div key={i}> {item.date} </div>);
}
uiItems.push(<div key={item.name}> {item.name} </div>);
prev = item.date;
})
return uiItems;
Check this example:
const artists = [{
date: '1/1/2017',
name: 'Oasis'
},
{
date: '5/1/2017',
name: 'Blur'
},
{
date: '5/1/2017',
name: 'Simply Red'
},
{
date: '10/1/2017',
name: 'Ed Sheeran'
},
{
date: '10/1/2017',
name: 'Beyonce'
},
{
date: '15/1/2017',
name: 'Jay-z'
},
];
let prev = '';
artists.map((item,i) => {
if(item.date != prev){
console.log(item.date, item.name);
}else{
console.log(item.name);
}
prev = item.date;
})
Upvotes: 0
Reputation: 618
artists[index - 1].date
This is undefined at 0 index because there's nothing to reference.
render(){
{artists.map((artist, index) =>
{(index > 0 && artists[index].date !== artists[index - 1].date) && (
<p>{ artists[index]date }</p>
)}
<div className="artists" key={index}>
<p>{ artist.name }</p>
</div>
)}
}
Upvotes: 0
Reputation: 5487
The issue is in this line of code:
artists[index].date !== artists[index - 1].date
The value of the index
variable starts at 0
, so artists[index - 1]
will try to get the item at index -1
which is undefined, as your error says. You can fix this by adding an additional condition to check if you are at index 0
, and only do the index - 1
if you are not at index 0
.
index === 0 || artists[index].date !== artists[index - 1].date
Upvotes: 0