How to render nested map from JSON in React.JS? - javascript

I'm trying to render menu values from a JSON. Considering that this is a multilevel menu, I'm trying to do a nested map to render the full menu.
const menu = {
data:[
{
title: "Home",
child: [
{
title: "SubLevel1",
child: {
title: "SubSubLevel1"
}
},
{
title: "SubLevel2",
child: [
{title: "SubSubLevel1"},
{title: "SubSubLevel2"}
]
}
]
},
{
title: "About",
},
{
title: "Contact",
}
]}
And here is the part when I use the map function :
const MenuNavigation = () => {
return (
{menu.data.map((item) => (
<div>
<ul>{item.title}</ul>
{item.map((sub, id) =>
<li>{sub.child[id].title}</li>
)}
</div>
))}
)
};
I managed to render main level for the menu (Home, About, Contact), but how can I print sublevel & subsublevel values?
Another question: Is there a way to map recursively a tree structure?

Try below:
The menu should be like this. Your menu had one issue in child of "SubLevel1" not defined inside an array.
const menu = {
data: [
{
title: "Home",
child: [
{
title: "SubLevel1",
child: [{
title: "SubSubLevel1",
}],
},
{
title: "SubLevel2",
child: [{ title: "SubSubLevel1" }, { title: "SubSubLevel2" }],
},
],
},
{
title: "About",
},
{
title: "Contact",
},
],
};
Render it recursively like below. I have added a margin-left to see the levels properly.
const renderMenu = (menu) => {
return menu.map((item) => (
<div style={{ marginLeft: '25px' }}>
{item.title}
{item.child && renderMenu(item.child)}
</div>
))
}
return <div>{renderMenu(menu.data)}</div>;

Related

ReactJs with Ant-Design page is opened checkbox selected in Table

I am creating a React project with Ant-Design. When the page is opened, I want several checkboxes in the table to be selected.
Hook:
const [selectedPages, setSelectedPages] = useState([]);
RowSelection :
let rowSelectionPages = {
selectedPages,
onChange: onSelectPagesChange,
selections: [Table.SELECTION_ALL, Table.SELECTION_NONE],
getCheckboxProps: (record) => {
return {
disabled: record.screenName === 'Disabled',
name: record.screenName,
defaultChecked: record.key === 1
};
},
};
onSelectPagesChange:
let onSelectPagesChange = (newSelectedPages) => {
setSelectedPages(newSelectedPages);
};
Columns:
let columnsPages = [
{
title: "screen name",
dataIndex: "screenName",
render: (text) => <a>{text}</a>,
},
];
MyData:
let dataPages = [
{
key: "1",
screenName: "Home",
},
{
key: "2",
screenName: "Login",
},
{
key: "3",
screenName: "profile",
},
{
key: "4",
screenName: "Disabled",
},
];
Table:
<Table
rowSelection={{
type: "checkbox",
...rowSelectionPages,
}}
columns={columnsPages}
dataSource={dataPages}
/>
I am using Ant-Design library for the first time.
This is the code I tried.
But I couldn't come to a conclusion.
Add row indexes in the state selection
const [selectedPages, setSelectedPages] = useState([0]); // check first row
demo

Update only one parameter when array is in array React

I have state data like that:
const IssuesList = () => {
const [issuesList, setIssuesList] = useState([
{
groupId: 0,
groupData: "19-07-2016",
groupIssues: [
{
id: 0,
title: "Page changes",
star: true,
},
{
id: 1,
title: "Review of last issues",
star: true,
},
],
},
{
groupId: 1,
groupData: "18-07-2016",
groupIssues: [
{
id: 2,
title: "Visual UI Update Review",
star: false,
},
{
id: 3,
title: "Sidebar changes",
star: false,
},
],
},
{
groupId: 2,
groupData: "15-07-2016",
groupIssues: [
{
id: 4,
title: "Crash update",
star: false,
},
{
id: 5,
title: "Page visual UI Update Review",
star: true,
},
{
id: 6,
title: "Sidebar update",
star: false,
},
],
},
{
groupId: 3,
groupData: "14-07-2016",
groupIssues: [
{
id: 7,
title: "Crash issue",
star: true,
},
{
id: 8,
title: "Visual update & Crash resolve",
star: true,
},
{
id: 9,
title: "Header changes",
star: false,
},
],
},
]);
return (
<div className="issuesList">
{issuesList.map((group) => (
<IssueGroup
key={group.groupId}
group={group}
setIssuesList={setIssuesList}
issuesList={issuesList}
/>
))}
</div>
);
};
export default IssuesList;
I want to change only the star parameter in groupIssues when I click an specyfic icon in a StarIcon component:
const StarIcon = ({ star, index, id, setIssuesList, issuesList, group }) => {
const issueStar = group.groupIssues[index].star;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
//xmlns:xlink="http://www.w3.org/1999/xlink"
preserveAspectRatio="xMidYMid"
width="17"
height="16"
viewBox="0 0 17 16"
className={star ? "staricon filled" : "staricon unfilled"}
onClick={() => {
// setIssuesList() TODO
}}
>
<path
d="M8.500,0.000 L11.301,5.028 L16.999,6.112 L13.033,10.302 L13.753,16.000 L8.500,13.561 L3.247,16.000 L3.967,10.302 L0.001,6.112 L5.699,5.028 L8.500,0.000"
fill={star ? "#21233d" : "#fff"}
stroke={star ? "none" : "#e0e0e0"}
/>
</svg>
);
};
export default StarIcon;
What is the best way to do that in that set of data. I know that spread operator can be helpfull there but i have no idea how to implement that in this data structure when We have array of objects and then we need to get into another array in specyfic object. Maybe changing the data structure will be better or breaking it into two arrays?
Ui view here
You should use a deep copy of the object, spread operator creates only a shallow copy.
Example:
const changeStar = (grpId, issueId) => {
setIssuesList(list => (
list.map(item => (
item.groupId === grpId ?
{...item, groupIssues: item.groupIssues.map(issue => (
issue.id === issueId ? {...issue, star: !issue.star} : issue
))}
: item
)
))
}
Explanation:
map function creates an entirely new array. It will return the elements as it is unless you have matching groupIds. Inside the groupIds, you will change the item based on the id. In the end, you deep copied the object and set an entirely new array with the desired result.
I think you need something like this: copy of current issuesList state. Update the specific star from that copy of state. Set the updated state.
Here is a working sandbox example: https://codesandbox.io/s/bold-browser-cnfw3?file=/src/App.js

Data object with array of different groups, trying to display ordered by group type using .map() and pushing to new array nothing is rendering

I have a React Component I'm building out that accepts a data object as props.
This data object holds an array of groups each with it's own group type.
What I'm trying to do is map over each group in the array and display it's contents as it's own section, but I also want to ensure that all of the objects with the type grid display together, followed by any sections with a list type no matter what position they are in the array.
So even if the array holds the values like:
[{Grid}, {List}, {Grid}, {List}, {Grid}]
It will display like this:
Group #1 (Grid)
Contents
Group #2 (Grid)
Contents
Group #3 (Grid)
Contents
Group #4 (List)
Contents
Group #5 (List)
Contents
The problem I'm having is that I wrote two separate components to handle the styling of each type and then passed them into a function that creates a new array from the original to then render, and nothing is displaying. I'm not getting any error messages or anything in the console so I'm completely stumped as to where I'm going wrong.
Here are my components, the data structure and a Codesandbox:
// Component File
import "./styles.css";
import data from "./resourceMock";
import FileIcon from "./FileIcon";
const GridView = (group) => {
const { groupName, items } = group;
return (
<>
<h2>{groupName}</h2>
<ul
style={{
display: "inline-flex",
flexWrap: "wrap",
listStyleType: "none"
}}
>
{items.map((item) => {
return (
<li style={{ height: "40vh", flexGrow: 1 }}>
<img src={item.img} style={{ height: "150px", width: "150px" }} />
<h4>{item.name}</h4>
<h5>{item.subtitle}</h5>
</li>
);
})}
</ul>
</>
);
};
const ListView = (group) => {
const { groupName, items } = group;
return (
<>
<h2>{groupName}</h2>
<ul style={{ listStyleType: "none" }}>
{items.map((item) => {
return (
<li>
<FileIcon />
{item.title}
</li>
);
})}
</ul>
</>
);
};
function renderList(group) {
const lists = [];
if (!group) return null;
data.map((group) => {
switch (group.groupType) {
case "grid":
return lists.push((group) => {
<GridView group={group} />;
});
case "list":
return lists.push((group) => {
<ListView group={group} />;
});
default:
return lists.push((group) => {
<ListView group={group} />;
});
}
});
return lists;
}
export default function App() {
return <div className="App">{data.map((group) => renderList(group))}</div>;
}
Data Structure:
export default [
{
groupName: "Marvel Characters",
groupType: "grid",
items: [
{
name: "Iron Man",
subtitle: "Inventor Tony Stark",
img:
"https://www.denofgeek.com/wp-content/uploads/2019/02/mcu-1-iron-man.jpg?resize=768%2C432"
},
{
name: "Incredible Hulk",
subtitle: "Bruce Banner",
img:
"https://lh3.googleusercontent.com/proxy/-jHnFcGLqlxjdOl9Mf99UPBk4XJKcQ1Hsv7lPYEs8Vai874sW0l5TUwn3acriwGpE36aUDPpZHPFzccRUt7b7POGOWCFIbgYomTO9bDCXF0eovxFGdr_D3P-0wfLnkUMOOJDG09MgAzqSCbiDq-A"
}
]
},
{
groupName: "Magic Cards",
groupType: "list",
items: [
{
title: "Kamahl, Fist Of Krosa",
link:
"https://gatherer.wizards.com/pages/card/Details.aspx?multiverseid=220490"
},
{
title: "Seedborn Muse",
link:
"https://gatherer.wizards.com/pages/card/Details.aspx?multiverseid=446180"
}
]
},
{
groupName: "DC Characters",
groupType: "grid",
items: [
{
name: "Batman",
subtitle: "Bruce Wayne",
img:
"https://static.wikia.nocookie.net/marvel_dc/images/a/a6/Batman_Vol_2_2_Variant_Textless.jpg/revision/latest/top-crop/width/360/height/450?cb=20120228075313"
},
{
name: "Martian Manhunter",
subtitle: "J'onn J'onzz",
img:
"https://cdn.flickeringmyth.com/wp-content/uploads/2021/03/Martian-Manhunter-600x338.png"
}
]
},
{
groupName: "Kaiju and Mechs",
groupType: "grid",
items: [
{
name: "Godzilla",
img:
"https://www.denofgeek.com/wp-content/uploads/2019/05/godzillakingofmonsters-2.jpg?resize=768%2C432"
},
{
name: "Hunter Vertigo",
img: "https://i.ytimg.com/vi/7F-iZYAqSbw/maxresdefault.jpg"
}
]
},
{
groupName: "Comic Books",
groupType: "list",
items: [
{
title: "Descender",
link: "https://imagecomics.com/comics/series/descender"
},
{
title: "East of West",
link: "https://imagecomics.com/comics/series/east-of-west"
},
{
title: "Letter 44",
link: "https://onipress.com/collections/letter-44"
}
]
}
];
I have fixed the issues.
Sandbox: https://codesandbox.io/s/affectionate-sinoussi-5suro
You are already looping over data inside renderList, so we can directly have <div className="App">{renderList(data)}</div>;
To sort, we can use Array.sort()
data.sort((a, b) => a.groupType === b.groupType ? 0 : a.groupType > b.groupType ? 1 : -1);
Also, in switch case you need to push the component and not a function.
case "grid":
lists.push(<GridView group={group} />);
break;
Need to use destructuring here const ListView = ({ group }) => {}
Finally add key to your lists. I have added using the item name, but you need to change per your requirement.

Not All Default Parameters Are Rendering

Can anyone tell me why one default parameter renders the default value while the other doesn't?
Take this Nav component and its' props being passed. The heading prop renders however the props inside of navItems array does not. It will console log as undefined.
Note: If I remove/comment out all the props inside navItems, the default values do render.
Component:
const Nav = ({
heading = "This Default Value Renders!",
navItems = [
{
id: 0,
label: "This Value Does Not Render",
subMenu: [
{
id: 1,
item: "This doesn't either",
},
],
},
],
}) => {
return (
<h1>{heading}</h1>
{navItems.map(item => (
<li>
{item.label}
<ul>
{item.subMenu.map(subItem => (
<li>{subItem.item}</li>
))}
</ul>
</li>
))}
)
}
Component used in parent component with props being passed.
<Nav
// heading = "I am the heading",
navItems = {[
{
id: 1,
// label: "About",
subMenu: [
{
id: 1,
// item: "About Sub Item 1",
},
{
id: 2,
// item: "About Sub Item 2",
},
],
},
]}
/>

Create a key map for all paths in a recursive/nested object array

I have an n levels deep nested array of tag objects with title and ID. What I'm trying to create is a an object with IDs as keys and values being an array describing the title-path to that ID.
I'm no master at recursion so my attempt below doesn't exactly provide the result I need.
Here's the original nested tag array:
const tags = [
{
title: 'Wood',
id: 'dkgkeixn',
tags: [
{
title: 'Material',
id: 'ewyherer'
},
{
title: 'Construction',
id: 'cchtfyjf'
}
]
},
{
title: 'Steel',
id: 'drftgycs',
tags: [
{
title: 'Surface',
id: 'sfkstewc',
tags: [
{
title: 'Polished',
id: 'vbraurff'
},
{
title: 'Coated',
id: 'sdusfgsf'
}
]
},
{
title: 'Quality',
id: 'zsasyewe'
}
]
}
]
The output I'm trying to get is this:
{
'dkgkeixn': ['Wood'],
'ewyherer': ['Wood', 'Material'],
'cchtfyjf': ['Wood', 'Construction'],
'drftgycs': ['Steel'],
'sfkstewc': ['Steel', 'Surface'],
'vbraurff': ['Steel', 'Surface', 'Polished'],
'sdusfgsf': ['Steel', 'Surface', 'Coated'],
'zsasyewe': ['Steel', 'Quality']
}
So I'm building this recursive function which is almost doing it's job, but I keep getting the wrong paths in my flat/key map:
function flatMap(tag, acc, pathBefore) {
if (!acc[tag.id]) acc[tag.id] = [...pathBefore];
acc[tag.id].push(tag.title);
if (tag.tags) {
pathBefore.push(tag.title)
tag.tags.forEach(el => flatMap(el, acc, pathBefore))
}
return acc
}
const keyMap = flatMap({ title: 'Root', id: 'root', tags}, {}, []);
console.log("keyMap", keyMap)
I'm trying to get the path until a tag with no tags and then set that path as value for the ID and then push the items 'own' title. But somehow the paths get messed up.
Check this, makePaths arguments are tags, result object and prefixed titles.
const makePaths = (tags, res = {}, prefix = []) => {
tags.forEach(tag => {
const values = [...prefix, tag.title];
Object.assign(res, { [tag.id]: values });
if (tag.tags) {
makePaths(tag.tags, res, values);
}
});
return res;
};
const tags = [
{
title: "Wood",
id: "dkgkeixn",
tags: [
{
title: "Material",
id: "ewyherer"
},
{
title: "Construction",
id: "cchtfyjf"
}
]
},
{
title: "Steel",
id: "drftgycs",
tags: [
{
title: "Surface",
id: "sfkstewc",
tags: [
{
title: "Polished",
id: "vbraurff"
},
{
title: "Coated",
id: "sdusfgsf"
}
]
},
{
title: "Quality",
id: "zsasyewe"
}
]
}
];
console.log(makePaths(tags));

Categories

Resources