Loop inside JSX - javascript

I have an object similar to this:
{
id: number,
kids: [{
id: number,
kids: [{
id: number,
kids: []
}]
}]
}
So it has property kids which is an array of kids each of which might have its own array of kids. I need to render original object in a tree view list like this:
<ul>
{object.map(item => (
<li>
<p>{item.value}</p>
{item.kids ?
{item.kids.map(item => (
<ul>
<li>
<p>{item.value}</p>
</li>
</ul>
))}
: null
}
</li>
))}
</ul>
So every item of kids will be a <li></li> element with <ul></ul> inside of it if item.kids isn't empty array.
I could keep going like this but it's pretty messy and more importantly I don't know when exactly kids property will be an empty array. So I need somehow loop over original object and it's properties and do something like
while (kid.kids) {
return {kid.kids.map(kid => (
<li>
<p>{kid.value}</p>
<ul>
kid.kids.map(kid => (
etc
))
</ul>
</li>
}))
}
But I can't understand the better way to loop like this.

This is probably best solved with recursion.
const Kids = ({id, kids}) => {
return {
<li key={id}>
<p>{id}</p>
{kids
? (<ul>{kids.map((kid) => <Kids id={kid.id} kids={kid.kids} />)}</ul>)
: null;
}
</li>
}
};

Related

How to map a nested object

I have to populate the li element with data stored as a JSON object.
With "title" it works simple. But it's not when talking about name's
values. How can I map the subMenu object to get the name?
<ul>
{data.map(({ title, subMenu }) => (
<li className="mobileMenu-body-nav-item">
<button className="mobileMenu-body-nav-item-btn">
*** here I have to put name ***
</button>
</li>
))}
</ul>
JSON object
[
{
"title": "Breeds",
"subMenu": [
{
"id": 1,
"name": "Dog Breeds"
},
{
"id": 2,
"name": "Cat Breeds"
}
]
},
{
"title": "About Pet Adoption",
"subMenu": [
{
"id": 3,
"name": "About Dog Adoption"
},
{
"id": 4,
"name": "About Cat Adoption"
}
]
}
]
As noted in the comments on the accepted answer div elements are not valid children of buttons (related question) and while one should assign a key to mapped elements in React using the index of the iterated array is not always ideal. (see the Docs or related article from T.J.Crowder's comment).
Given that you are mapping a nested list it seems more appropriate to structure it as such. Here using the title as the outer li key (though a more definite unique property would be better) and the subMenu.id as a key for the inner li.
<ul>
{data.map(({ title, subMenu }) => (
<li key={title} className='mobileMenu-body-nav-item'>
<ul>
{subMenu.map(({ id, name }) => (
<li key={id}>
<button className='mobileMenu-body-nav-item-btn'>{name}</button>
</li>
))}
</ul>
</li>
))}
</ul>
You can just call map again, like this:
<ul>
{data.map(({ title, subMenu }) => (
<li className="mobileMenu-body-nav-item">
<button className="mobileMenu-body-nav-item-btn">
{subMenu.map(({ name }) => (<span>{name}</span>))}
</button>
</li>
))}
</ul>
Change the <span> tag to match however you want this content to be rendered.
Also, if this is React, don't forget to set the key prop appropriately when using map:
<ul>
{data.map(({ title, subMenu }) => (
<li key={title} className="mobileMenu-body-nav-item">
<button className="mobileMenu-body-nav-item-btn">
{subMenu.map(({ name }) => (<div key={name}>{name}</div>))}
</button>
</li>
))}
</ul>

How to call the recursive call at last if it meets the condition?

I have nested object called option. It has nested (potentially infinite) objects called childOptions key.
option = {
name: 'o1', label: 'o1', childOptions: [
{ name: 'o4', label: 'o4', childOptions: [
{name: 'o5', label: 'o5',
]},
{ name: 'o2', label: 'o3'},
{ name: 'o3', label: 'o3'}
]
}
This is my render function to render JSX based on that.
function renderOption(option): JSX.Element {
if (option.childOptions) {
return (
<li
key={option.name}
>
<span>
{option.label}
</span>
<ul>
{option.childOptions.map(renderOption} << recursive call!
</ul>
</li>
);
}
I have nested option object which passes into renderOption
This is the current output (CORRECT But not what I want)
<li>
<span>o1</span>
<ul>
<li>
<span>o4</span>
<ul>
<li><span>o5</span></li>
<ul>
</li>
<li><span>o2</span></li>
<li><span>o3</span></li>
</ul>
</li>
Because we show o2 and o5 right after o1, user gets confused to read.
This is what I need
<li>
<span>o1</span>
<ul>
<li><span>o2</span></li>
<li><span>o3</span></li>
<li>
<span>o4</span>
<ul>
<li><span>o5</span></li>
<ul>
<li>
</ul>
</li>
I think it might be not possible. How do we call the recursive call at last if it meets the condition?
I still want to keep the recursive call if we could.
Sort the childOptions in your object by name:
function renderOption(option): JSX.Element {
if (option.childOptions) {
option.childOptions.sort((a, b) => a.name.localeCompare(b.name));
return (
<li
key={option.name}
>
<span>
{option.label}
</span>
<ul>
{option.childOptions.map(renderOption} << recursive call!
</ul>
</li>
);
}
You could return a ul for an array of options. Get the array of options as props to the function component. Pass the initial option object as an array to make it uniform and easier to make a recursive call
Here's a runnable snippet:
const option={name:"o1",label:"o1",childOptions:[{name:"o4",label:"o4",childOptions:[{name:"o5",label:"o5"}]},{name:"o2",label:"o2"},{name:"o3",label:"o3"}]};
function Display({ options }) {
return (
<ul>
{options.map(c => (
<li key={c.name}>
<span>{c.label}</span>
{c.childOptions && <Display options={c.childOptions} />}
</li>
))}
</ul>
);
}
ReactDOM.render(
<Display options={[option]} />,
document.getElementById("root")
);
<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="root"></div>

Use conditional logic in an array map?

Ok so I am using express-react-views as a templating engine and I am currently trying to make a working breadcrumb. On each route I pass a prop called "crumb" that is an array of the current location on the app. That array looks like this:
[
{
text: "Home",
href:"/",
active: false
},
{
text: "Step2",
href:`/page`,
active: true
}
]
Obviously this can be multiple steps down. The last step is the page you are on, so active is set to true. This is where my problem is. To render this on the page I am mapping this array to JSX like this:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
This code works fine but what the active page should have the class "active" on it and should not have an "a" tag. So what I need to do it as it's mapping this array to check for the active:true value and then map a different element. I hope that makes sense.
Hi you can try this out if you want both active and inactive links to be shown:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
crumb.active ? <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li> : <li class="breadcrumb-item">{crumb.text}</li>
)}
</ol>
)
}
if you only want to show active links then you can use:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.filter(item => item.active).map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
Inside map you can check crumb.active, so it will either return true or false and based on that you can return respective element.
Is this what you want
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb => {
if(crumb.active)
return <li class="breadcrumb-item active"></li>
else
return <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
})}
</ol>
)
}

Mapping an object of array in Reactjs

I have an object each key of the object has an array value
const correctionsWords = {
"word": [ "1" , "2"] ,
"word2": ["20" ,"22" ]
};
I did map through each key by using the following code
let correctionList = Object.keys(correctionsWords).map( (key) => {
return (
<div>
{
//console.log( 'After Mapping ' , correctionsWords[key]) [1,2,3]
<ul>
<li>{key} { /* word */}
<ul>
<li>{correctionsWords[key]}</li>
</ul>
</li>
</ul>
}
</div>
); });
the result is * key: word
* value: 1 2
How can I list the values of the array?
Map again each array element:
<ul>
{correctionsWords[key].map(el => (
<li key={el}>{el}</li>
))}
</ul>
I've used a key as the element here. If the elements are not unique better use another key. Also, you need another key for your object mapping in the topmost div:
return (
<div key={key}>
...
I think what you’re looking for is to replace that innermost li with:
{
correctionsWords[key].map(value => <li>{value}</li>)
}

How can I map through an object in ReactJS?

I have a response like this:
I want to display the name of each object inside this HTML:
{subjects.map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">{ item.name }</span>
</li>
))}
But it throws an error of subjects.map is not a function.
First, I have to define the keys of the objects where it creates an array of keys, where I want to loop through and show the subject.names.
What I also tried is this:
{Object.keys(subjects).map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subjects[i]}</span>
</li>
))}
When calling Object.keys it returns a array of the object's keys.
Object.keys({ test: '', test2: ''}) // ['test', 'test2']
When you call Array#map the function you pass will give you 2 arguments;
the item in the array,
the index of the item.
When you want to get the data, you need to use item (or in the example below keyName) instead of i
{Object.keys(subjects).map((keyName, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subjects[keyName]}</span>
</li>
))}
You get this error because your variable subjects is an Object not Array, you can use map() only for Array.
In case of mapping object you can do this:
{
Object.keys(subjects).map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">{ subjects[item].name }</span>
</li>
))
}
Use Object.entries() function.
Object.entries(object) return:
[
[key, value],
[key, value],
...
]
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
{Object.entries(subjects).map(([key, subject], i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subject.name}</span>
</li>
))}
Map over the keys of the object using Object.keys():
{Object.keys(yourObject).map(function(key) {
return <div>Key: {key}, Value: {yourObject[key]}</div>;
})}
I use the below Object.entries to easily output the key and the value:
{Object.entries(someObject).map(([key, val], i) => (
<p key={i}>
{key}: {val}
</p>
))}
Do you get an error when you try to map through the object keys, or does it throw something else.
Also note when you want to map through the keys you make sure to refer to the object keys correctly. Just like this:
{ Object.keys(subjects).map((item, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">key: {i} Name: {subjects[item]}</span>
</li>
))}
You need to use {subjects[item]} instead of {subjects[i]} because it refers to the keys of the object. If you look for subjects[i] you will get undefined.
I am not sure why Aleksey Potapov marked the answer for deletion but it did solve my problem.
Using Object.keys(subjects).map gave me an array of strings containing the name of each object, while Object.entries(subjects).map gave me an array with all data inside witch it's what I wanted being able to do this:
const dataInfected = Object.entries(dataDay).map((day, i) => {
console.log(day[1].confirmed);
});
I hope it helps the owner of the post or someone else passing by.
Also you can use Lodash to direct convert object to array:
_.toArray({0:{a:4},1:{a:6},2:{a:5}})
[{a:4},{a:6},{a:5}]
In your case:
_.toArray(subjects).map((subject, i) => (
<li className="travelcompany-input" key={i}>
<span className="input-label">Name: {subject[name]}</span>
</li>
))}

Categories

Resources