Rendering multiple states one after the other using .map - javascript

I'm having some difficulty with rendering using .map. What I'm trying to do is to render the artist name, the song title, and a link to a page where you can find sheet music. I have a separate state for each one of these categories and it looks like this:
this.state = {
currentSearch: "",
artistName: [],
songTitle: [],
tabId: [],
}
Here is my render:
render(){
return(
<div>
{this.props.artist.map((artist, i) =>{
return(
<h3>{artist}</h3>
)
})}
{this.props.title.map((title, i) => {
return (
<h3>{title}</h3>
)
})}
{this.props.link.map((link, i) => {
return (
<h3>{link}</h3>
)
})}
</div>
)
}
The main problem that I'm having is that the information will display on the page like this:
The Beatles
The Beatles
The Beatles
All You Need Is Love
Blackbird
Twist and Shout
www.allyouneedisloveurl.com
www.blackbirdurl.com
www.twistandshouturl.com
Is there a way I can map these so that they appear one after the other like this?
The Beatles
All You Need Is Love
www.songurl.com
Thank you!!

You can use the index to render the other data. Try
render(){
return(
<div>
{this.props.artist.map((artist, i) =>{
return(
<div key={i}>
<h3>{artist}</h3>
<h3>{this.props.title[i]}</h3>
<h3>{this.props.link[i]}</h3>
</div>
)
})}
</div>
)
}

You can put them all together in one map like this:
render(){
return(
<div>
{
this.props.artist.map((artist, i) =>{
return(
<div key={i} className="artist">
<h3>{artist}</h3>
<h3>{this.props.title[i]}</h3>
<h3>{this.props.link[i]}</h3>
</div>
)
})
}
</div>
)
}

Related

Best ways of rendering a nested JSON in react?

I have a JSON file that I want to render in react with unknown keys and values (just their types):
{
"Day 1" :{
"Chest": {
"warmUp": ["parallel bar dips"],
"main": ["Bench Press", "Inclined Bench press", "Decline Bench press"],
"secondary": ["Dumbbell Flys", "Cable Crossover Flys", "Pec-deck Fly"]
},
"Biceps" : {
"Lola": ["Barbell Curl", "Preacher Curl"],
"bobo": ["Hammer Curls", "Cable Curl", "Dumbbell Curl"]
}
}
I want to Render them so they look like this
<h2>Day </h2>
<h4>Chest</h4>
<h5>warmUp</h5>
<ul>
<li>Bench Press</li>
<li>Inclined Bench press</li>
<li>Decline Bench press</li>
</ul>
<h5>secondary</h5>
<ul>
<li>Dumbbell Flys</li>
<li>Cable Crossover Flys</li>
<li>Pec-deck Fly</li>
</ul>
I have a solution https://codesandbox.io/s/cold-sun-vprkl?file=/src/App.js
Object.entries(program).map((arrays) => {
return arrays.map((renderArr, render_idx) => {
if (render_idx === 0) {
return render.push(<h2>{renderArr}</h2>);
} else {
for (const [muscleGroup, exerciseGroups] of Object.entries(renderArr)) {
render.push(<h4>{muscleGroup}</h4>);
for (const exerciseCategory in exerciseGroups) {
render.push(<h5>{exerciseCategory}</h5>);
exerciseGroups[exerciseCategory].map(
(exercise, exercise_idx) => {
return render.push(<li>{exercise}</li>);
}
);
;
}
}
}
});
});
but I want to know if there is a better, more elegant way to do this. Also, in my solution I couldn't wrap the ul tags around the list items.
I would split the logic between appropriate small components to improve readability and get closer to the first SOLID principle - single responsibility.
const data = {
'Day 1': {
Chest: {
warmUp: ['parallel bar dips'],
main: ['Bench Press', 'Inclined Bench press', 'Decline Bench press'],
secondary: ['Dumbbell Flys', 'Cable Crossover Flys', 'Pec-deck Fly'],
},
Biceps: {
Lola: ['Barbell Curl', 'Preacher Curl'],
bobo: ['Hammer Curls', 'Cable Curl', 'Dumbbell Curl'],
},
},
}
const Program = ({ program }) => {
const days = Object.entries(program)
return (
<section className="Program">
{days.map(([name, data], index) => (
<ProgramDay name={name} data={data} key={index} />
))}
</section>
)
}
const ProgramDay = ({ name, data }) => {
const parts = Object.entries(data)
return (
<div className="ProgramDay">
<h2>{name}</h2>
{parts.map(([name, data], index) => (
<ProgramPart name={name} data={data} key={index} />
))}
</div>
)
}
const ProgramPart = ({ name, data }) => {
const types = Object.entries(data)
return (
<div className="ProgramPart">
<h2>{name}</h2>
{types.map(([name, exercises], index) => (
<ProgramExercises name={name} exercises={exercises} key={index} />
))}
</div>
)
}
const ProgramExercises = ({ name, exercises }) => {
return (
<div className="ProgramExercises">
<h5>{name}</h5>
<ul>
{exercises.map((name, index) => (
<li key={index}>{name}</li>
))}
</ul>
</div>
)
}
ReactDOM.render(<Program program={data} />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Your approach can work, but it's very un-React-like. A far more React-like approach would leverage JSX much more heavily (and use logic inside JSX), and it would also use multiple components and return instead of building arrays.
Imagine instead ...
const Main = () =>
Object.entries(program).map(arrays =>
arrays.map((renderArr, index) =>
{index === 0
? <h2>{muscleGroups}</h2>
: <MuscleGroups muscleGroups={muscleGroups} />
}
);
const MuscleGroups = ({ muscleGroups }) =>
Object.entries(muscleGroups).map(([muscleGroup, exerciseGroups]) =>
<MuscleGroup muscleGroup={muscleGroup} exerciseGroups={exerciseGroups}/>
);
const MuscleGroup = ({ muscleGroup, exerciseGroups }) =>
<>
<h4>{muscleGroup}</h4>
{Object.entries(exerciseGroups).map(([exerciseCategory, exerciseGroup]) =>
<ExcerciseGroup category={exerciseCategory} exerciseGroup={exerciseGroup}/>
)}
</>
const ExerciseGroup = ({ exerciseCategory, exerciseGroup }) =>
<>
<h5>{exerciseCategory}</h5>
<ul>
{exerciseGroup.map(exercise => <li>{exercise}</li>)}
</ul>
</>;
P.S. Apologies if there are any typos in there (it's hard to refactor lots of code outside an editor). But even if there are some, hopefully you can see the overall idea I'm trying to convey.
You don't have to use as many components as I did (eg. you could combine MuscleGroups and MuscleGroup if you wanted). Like anything in code, it's ultimately subjective as to what's the "best" way.
But in a larger sense, you should really have the same guiding principle as with regular Javascript code. Just as you wouldn't want one giant function that does everything in JS (you'd want several smaller, focused functions), you likewise want to break your React logic into several focused components.

Destructuring argument for key along with object in javascript

How can I get the passed in object course along with the id key using destructuring the argument as in following code snippet ?
...
return (
<div>
{course.map(course => {
return <Course key={course.id} course={course} />;
})}
</div>
);
For instance I've tried like(see below) this, but its not valid
...
return (
<div>
{course.map(({id} = course) => {
return <Course key={id} course={course} />;
})}
</div>
);
The object for reference
const course = [
{
name: "Half Stack application development",
id: 1
},
{
name: "Node.js",
id: 2
}
Are there any way to do this or is it not possible yet?
Destructure id and spread the rest
course.map( ({ id, ...item }) => (
<div id={id}> {item.foo} </div>
))
You can't destruct an object into its an element and itself.
It could be better destruct item in the callback function like below.
console.log('-------Only get rest obj------');
const courses = [{ name: "Half Stack application development", id: 1 }, { name: "Node.js", id: 2 }];
courses.forEach(({id, ...item}) => console.log('rest obj:', item));
console.log('----Get full obj and destruct---------');
courses.forEach(item => {
const { id } = item;
console.log('id:', id);
console.log('item:', item);
});
return (
<div>
{course.map(item => {
return <Course key={item.id} course={item} />;
})}
</div>
);
You are trying to use the course variable again inside map function.
Rename outer most variable from course to courses.
return (
<div>
{courses.map(course => {
return <Course key={course.id} course={course} />;
})}
</div>
);
Consider below example
return (
<div>
{course.map(({ id, ...course }) => {
return <Course key={id} course={course} />;
})}
</div>
);
It does gets us the id but course object does have all the keys of the original object(ie, it doesn't have the id now).

TypeError: Cannot read property 'map' of null

I couldn't understand why...here is the GitHub repository: https://github.com/Dronrom/React-test
That’s because you initialized peopleList as null in your component. So map works only on arrays so you need to check peopleList whether its really an array before doing map on it so
Change
renderItems(arr) {
return arr.map(({id, name}) => {
return (
<li className="list-group-item"
key={id}
onClick={() => this.props.onItemSelected(id)}>
{name}
</li>
);
});
}
To
renderItems(arr) {
if(arr){
return arr.map(({id, name}) => {
return (
<li className="list-group-item"
key={id}
onClick={() => this.props.onItemSelected(id)}>
{name}
</li>
);
});
}
}
I think your issue may be that react renders once before componentDidMount(). This is an issue because your calling map on arr which is null. const { peopleList } = this.state; you set people list to your current state which you set as default to be null, state = {peopleList: null}; then you later call this.renderItems(peopleList); which people list is still null at this moment so you are getting the Cannot read property 'map' of null error.
I belive something like componentWillMount is what you need instead. I recommend looking at this post which has a similar issue of react life cycle methods. React render() is being called before componentDidMount()
the answer is very simple: the type of the input isn't array type, it might be null or undefined. so that it doesn't have .map function.
How to fix:
Make sure your input must be array type before call renderItems().
render(){
const { peopleList } = this.state;
const items = (peopleList && peopleList.length) ? this.renderItems(peopleList) : null;
return(
<ul className="item-list list-group">
{items}
</ul>
);
}
Or:
Make sure your input must be array type before do mapping:
renderItems(arr) {
return !arr ? null : arr.map(({id, name}) => {
return (
<li className="list-group-item"
key={id}
onClick={() => this.props.onItemSelected(id)}>
{name}
</li>
);
});
{product.size?.map(c=>(
<FilterSizeOption key={c}>{c}</FilterSizeOption>
))}
Wrapping the return statement with a if statement worked for me
So changed
return (
<div>
<Navbar />
{countries.map((country, i) => {
return (
<div>
<span key={`${country.name.common}${i}`}>
{country.name.common}
</span>
</div>
);
})}
</div>
);
to this
if (countries) {
return (
<div>
<Navbar />
{countries.map((country, i) => {
return (
<div>
<span key={`${country.name.common}${i}`}>
{country.name.common}
</span>
</div>
);
})}
</div>
);
}

Pass index as state to component using React

I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910

React won't render within map function

I'm trying to render an array containing some objects using the JS function map().
However when I return the text nothing is shown:
console.log(this.props.project.projects); // (2) [{…}, {…}]
this.props.project.projects.map((item, index) => {
console.log(item.projectDescription); //"Testproject"
return (
<div key={index}>
{item.projectDescription}
</div>
)
})
I just don't get it, why there is no text shown, since the console.log(item.projectDescription) shows exactly what I want to display.
Update:
It works when I change it to this:
return this.props.project.projects.map((item, index) => (
<div key={index} style={{ color: '#fff' }}>
{item.projektBeschreibung}
</div>
))
I already thought about using the foreach-method but I think it should actually work using the map()-function.
Here you can see also the render method of my Component.
class ProjectRow extends Component {
renderProjects() {
console.log(this.props.project);
if (this.props.project.loading) {
return (
<div style={{color: '#fff'}}>
Loading
</div>
)
} else {
console.log(this.props.project.projects);
this.props.project.projects.map((item, index) => {
console.log(item);
console.log(item.projektBeschreibung);
console.log(index);
return (
<div key={index}>
{item.projektBeschreibung}
</div>
)
})
}
}
render() {
return (
<div>
{this.renderProjects()}
</div>
);
}
}
The renderProjects function is not returning anything when it hits your else case. Here is an example of use:
renderProjects() {
console.log(this.props.project);
if (this.props.project.loading) {
return (
<div style={{color: '#fff'}}>
Loading
</div>
)
} else {
console.log(this.props.project.projects);
// added return statement here
return this.props.project.projects.map((item, index) => {
console.log(item);
console.log(item.projektBeschreibung);
console.log(index);
return (
<div key={index}>
{item.projektBeschreibung}
</div>
)
})
}
}
why not use map like below ?
render(){
return(
<div>
{this.props.project.projects.map((item, index) => (
<div key={index}>
{item.projectDescription}
</div>
))}
</div>
)
}

Categories