Loop through JSON in JSX - javascript

I have a tree like JSON structure and it can be n levels deep. Here is an example:
"plot": {
"population": "All",
"gates": [
{
"name": "population1",
"plot": {
"population": "population1",
"gates": [
{
"name": "population3",
"plot": {
"population": "population3",
}
}
]
}
},
{
"name": "population2",
"plot": {
"population": "population4",
}
}
]
};
It starts with plot. The is the top level. A plot can have many gates. These are essentially branches. Each gate has another plot, which can have multiple gates etc.
I want to output the plot.population within JSX wrapped in a div. Here is my attempt (MultiStainState is a JSON file with the above JSON):
function Plot(props) {
...
const renderPlots = (plotObject) => {
console.log("plotObject is ", plotObject);
if (plotObject) {
return (
<>
<div>{plotObject.population}</div>
</>
);
}
{
plotObject.gates.map((gate, gateIndex) => {
plotObject(gate.plot);
});
}
};
return (
<div
style={{
height: "200px",
}}
>
Render Plots:
{renderPlots(MultiStainState)}
</div>
);
}
This output Render Plots:All and none of the child plot populations.
This is presumably because of the return within renderPlots(). I feel like I need to use recursion here (as I have attempted to do). But I cant figure out how....

The main issue with your renderPlots function is that if the given plotObject is non-null, the function just returns plotObject.population and never gets to the recursive step. There are some other issues with that function, but I'm going to offer a rewrite that will address those.
I'm not sure of your exact desired output format, but I'm going to use nesting <div> elements so that the DOM hierarchy matches the JSON structure.
You'll want to return a single JSX object with the recursive step within (recursion looks a bit weird in React/JSX compared to usual programming). I've also split the renderPlots function into a separate component, but that's more of a stylistic choice (I'll leave you to find a better name for the component).
Here's a simple example:
function PlotRender({ plotObject }) {
if (!plotObject) {
return null; // just in case
}
return (
<div>
{plotObject.population}
{plotObject.gates?.map(e => (
<PlotRender key={e.name} plotObject={e.plot}/>
))}
</div>
);
}
Which will render as (for the given sample data):
<div>
All
<div>
population1
<div>
population3
</div>
</div>
<div>
population4
</div>
</div>
Note also that in the outer <Plot> component, you'll likely need to pass MultiStainState.plot as the plotObject prop to <PlotRender> rather than just MultiStainState.

Here is a simple rendering of a recursive component based on this article. It checks if it has a gates array of length > 0 and if so will recursively render the component
sandbox
const plot = {
"population": "All",
"gates": [
{
"name": "population1",
"plot": {
"population": "population1",
"gates": [
{
"name": "population3",
"plot": {
"population": "population3",
}
}
]
}
},
{
"name": "population2",
"plot": {
"population": "population4",
}
}
]
}
const RecursiveComponent = ({ population, gates }) => {
const hasGates = gates && gates.length
return (
<React.Fragment>
<div>
{population}
</div>
{hasGates && gates.map(({name,plot}) => <RecursiveComponent key={name} population={plot.population} gates={plot.gates} />)}
</React.Fragment>
);
};
const App = props => {
return (
<RecursiveComponent population={plot.population} gates={plot.gates} /> //starting point
);
};
ReactDOM.render(<App />, document.getElementById("app"));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="app"></div>

Related

Iterate nested object to access the key and values in react js

I have a nested object which is getting from api response, need to iterate that nested object based keys and values and the structure like this,
Have tried but not getting the expected output.
Code: Api response
{
"dashboard": "Dashboard",
"users": "Users",
"page_builder": "Page Builder",
"filemanager": {
"brand": "Brand Images",
"manufacturer": "Manufacturer Images"
},
"catalog": {
"catalog_product": "Product"
},
"coupon": "Coupon",
"egift": "E-gifting",
"paymentconfig": {
"configuration": "Gateway Config",
},
"app": {
"app_general": "General Config",
"forceupdate_config": "Force Update Config",
},
"apppayment": "Kapture Category",
"kapturecrm": "Vertical Master",
"phpinfo": "PHP Info"
}
When i tried from my end, am getting the output like this,
Tried sample code:
{Object.keys(roletest).map((key, idx) => (
<CFormCheck
id="validationrole_access"
key={idx}
name="role_access"
label={roletest[key]}
value={roletest[key]}
onChange={handleChange}
aria-describedby="inputGroupPrepend"
/>
))}
My Expected output:
Dashboard
Users
Page Builder
filemanager
Brand Images
Manufacturer Images
catalog
Product
Coupon
E-gifting
paymentconfig
Gateway Config
app
General Config
Force Update Config
Kapture Category
Vertical Master
PHP Info
My output:
Dashboard
Users
Page Builder
Coupon
E-gifting
Kapture Category
Vertical Master
PHP Info
Please do my needs
It sounds like you want to create a nested list. Which can actually be done quite easily. You were on the right track using an object method to iterate over the properties but Object.entries might be a little easier.
So, the trick is to make sure you use separate components for the list, and the list items (List/ListItem). In List, as you iterate over the items check if an item is an object. If it is create a new list with the List component, otherwise return a list item.
const data={dashboard:"Dashboard",users:"Users",page_builder:"Page Builder",filemanager:{brand:"Brand Images",manufacturer:"Manufacturer Images"},catalog:{catalog_product:"Product"},coupon:"Coupon",egift:"E-gifting",paymentconfig:{configuration:"Gateway Config"},app:{app_general:"General Config",forceupdate_config:"Force Update Config"},apppayment:"Kapture Category",kapturecrm:"Vertical Master",phpinfo:"PHP Info"};
function Example({ data }) {
return <List list={data} />;
}
function List({ list }) {
return (
<ul>
{Object.entries(list).map((item, key) => {
return <ListItem key={key} item={item} />;
})}
</ul>
);
}
function ListItem({ item }) {
const [label, value] = item;
if (typeof value === 'object') {
return (
<li>
{label}
<List list={value} />
</li>
);
}
return <li>{value}</li>;
}
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Error: Objects are not valid as a React child. If you meant to render a collection of children, use an array instead. Getting data from JSON

guys! I'm using ReactJS to create a small website. Since I added the following code it starts showing an error: Objects are not valid as a React child. If you meant to render a collection of children, use an array instead.
Code:
import { useState, useEffect } from 'react';
import { motion, useAnimation } from 'framer-motion';
import './css/App.min.css';
import config from './config';
function App() {
return (
<div className="myskills">
<Skills skillType="coding" />
</div>
);
}
function Skills(props){
const skillType = props.skillType;
const result = config.skills.filter(skill => skill.cat == skillType);
console.log(result);
result.map((skill, index) => (
<div className="singleSkill" key={index}>
{skill.name} Level: {skill.level}
</div>
));
return (<div>{result}</div>);
}
config.json
{
"skills": [
{
"name": "HTML",
"level": 5,
"cat": "coding"
},
{
"name": "CSS",
"level": 5,
"cat": "coding"
},
{
"name": "PHP",
"level": 4,
"cat": "coding"
}
]
}
Any ideas what's the problem?
The return statement in your Skills component is basically just this:
return (config.skills.filter(skill => skill.cat == skillType));
hence the "Objects are not valid as a React child" error.
Since result.map doesn't modify the original array, a better solution might look something like this:
function Skills(props) {
const skillType = props.skillType;
const result = config.skills.filter(skill => skill.cat == skillType);
return (
<div>
{result.map((skill, index) => (
<div className="singleSkill" key={index}>
{skill.name} Level: {skill.level}
</div>
))}
</div>
);
}

Looping over an array as a value in a JSON (javascript/react)

I have a JSON of team members:
[
{
"name": "Allie Armstrong",
"title": "Head of Finance",
"teams": ["Finance", "Europe"]
},
....]
I map over it, it's fine.
{teamMembersList.map(teamMember => {
return(
<TeamMember
teamMembers={teamMember}
/>
)
})}
But the teams part comes together.
I would like to basically get teammember.teams[0], then teammember.teams[1] etc, so they can be in their own span. Instead of having them as FinanceEurope
The number of elements in this array varies.
TRY :
{ if(teamMembersList.teams && teamMembersList.teams.length) {
teamMembersList.teams.map(teamMember => {
return( <TeamMember teamMembers={teamMember} />)
})
} else {
return('')
}
}
Also you need to check if teamMembersList.teams have some value because if it returns undefined / null OR string then it will not work and will give you error.
In the end I moved my JSON data into the js file as a const.
Then:
{teamMembersList.map(x => {
return x.teams.map(teams => (
<li key={x.id}>
{teams}
</li>
))
})}
It works fine :)

React - how to set inline CSS for each element of map function?

I have an application in React where I'm trying to read in data from a JSON file. The JSON file is in the following format:
[
{
"name": "Max",
"age": "21",
},
{
"name": "Sam",
"age": "18",
}
........
]
I have successfully read in the right data and displayed it on my screen, like this:
function foo(){
const styling = css`
font-size: 30px;
`;
return(
<div>
{people.map((person, i) => <Format key={i} {...person} css={styling}/>)}
</div>
);
}
Although all the information correctly displays on the screen, the styling is not getting applied to each person. How could I change this?
EDIT
Format component:
function Format({name, age}){
return (
<div>
<h1>{name}</h1>
<h2>{age}</h2>
</div>
);
}
function Format({name, age, css}){
return (
<div css={css}>
<h1>{name}</h1>
<h2>{age}</h2>
</div>
);
}
you passed styled to your component but you didnt use them in your child component
and plus using idx as key is not the best practice. following article explains why.
https://reactjs.org/docs/lists-and-keys.html
if name is unique you can pass each items name to mapped children.

Using map() to iterate through a nested Prop in React

I'm currently using react to render a prop called area which looks like this:
[{
"id": 1,
"name": "Europe",
"Countries": [{
"id": 1,
"name": "Iceland",
"Cities": [{
"id": 1,
"name": "Selfoss"
}]
}, {
"id": 2,
"name": "Switzerland",
"Cities": [{
"id": 2,
"name": "Geneva"
}]
}]
}, {
"id": 2,
"name": "Asia",
"Countries": [{
"id": 3,
"name": "Japan",
"cities": [{
"id": 3,
"name": "Yokohama"
}]
}]
}]
UPDATE 2--
This WORKS:
class AreaBar extends Component {
constructor(props) {
super(props);
}
.....
renderCountries() {
return(
<div>
This is the country
</div>
)
}
renderContinents() {
return(
<div>
This is the continent
{this.renderCountries()}
</div>
)
}
render() {
return(
<div>
{this.renderContinents()}
</div>
);
}
}
This outputs:
This is the continent
This is the country
Incorporating a map, this WORKS
renderContinents(area) {
return(
<div>
{area.name}
</div>
)
}
render() {
return(
<div>
{this.props.areas.map(this.renderContinents)}
</div>
);
}
}
This outputs:
Europe
Asia
BUT when I include {this.renderCountries()}, it doesn't output anything, which I think is why I couldn't get the suggestions to work.
renderCountries() {
return(
<div>
This is the country
</div>
)
}
renderContinents(area) {
return(
<div>
{area.name}
{this.renderCountries()}
</div>
)
}
render() {
return(
<div>
{this.props.areas.map(this.renderContinents)}
</div>
);
}
}
On Firefox, both of the continents show up but "this is a country doesn't show up" instead I get a
unreachable code after return statement
When an expression exists after a valid return statement,
a warning is given to indicate that the code after the return
statement is unreachable, meaning it can never be run.
It seems like it's saying renderCountries can never be run. I'm still a bit confused about this but I think I'm going to try to separate the components and see if it fixes the issue.
Two things:
1) In the second block of code in your question, you're doing area.countries.map. The key on your area object is called Countries, not countries. area.countries should be undefined.
2) area.Countries is an array of objects, like you said in your question. So yes, you can map over them just fine. The problem is that each Country is an object, and thus, you're trying to render an object as a child of your <div> in your renderCountries function. If you only want to display the Country's name, you should do something like this:
renderCountries(country){
return(
<div>
{country.name}
</div>
)
}
Then you will see some meaningful output.
you have a typo, use area.Countries instead of area.countries
Also I think you should create 3 components at least: Area, Country and City. Then you can render the data like so (please note I use ES6 syntax) :
var areas = [/* array you posted above*/];
// in your top-level component
render() {
return (<div>
{areas.map((area) => {
return <Area data={area}/>;
})}
</div>);
}
// Area component
export function Area({data}) {
render() {
return (<div>
Area name: {data.name}
{data.Countries.map((country) => {
return <Country data={country}/>
})}
</div>);
}
}
// Country component
export function Country({data}) {
render() {
return (<div>
Country: {data.name}
{data.Cities.map((city) => {
return <City data={city}/>
})}
</div>);
}
}
// City component
export function City({data}) {
render() {
return (<div>
City: {data.name}
</div>);
}
}

Categories

Resources