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

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>);
}
}

Related

Loop through JSON in JSX

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>

setting state within nested response

I am building a react app and I am setting the state with api of nested response of nested state But the state is not setting the way I want.
response that is receiving from api
[
{
"id": 70,
"title": "feefifef",
"images": [
{
"id": 28,
"text": "First Image"
"blog_id": 70,
},
{
"id": 28,
"text": "First Image",
"blog_id": 70,
}
]
}
]
App.js
class App extends React.Component {
constructor(props){
super(props)
this.state = {
blogs = [
{
id: 0,
title: "",
images: [
{
id:0,
text:""
}
]
}
]
}
}
componentDidMount() {
let data;
axios.get('http://127.0.0.1:8000/api/blogs/').then((res) => {
data = res.data;
this.setState({
blogs: data.map((blog) => {
return Object.assign({}, blog, {
id: blog.id,
title: blog.title,
images: blog.images,
}
})
})
})
}
render() {
const blogs = this.state.blogs.map((blog) => (
<BlogList
id={blog.id}
title={blog.title}
images={blog.images}
/>
))
}
return (
<div>{blogs}</div>
)
}
class BlogList extends React.Component {
constructor(props){
super(props)
}
return (
<div>
Title: {this.props.title}
Images: {this.props.images}
</div>
)
}
What is the problem ?
Images are not showing after Title. I am trying to show all images in BlogList class of every blog.
I have also tried using (in BlogList class)
this.props.images.map((img) => {
return (
<div>
Title: {this.props.title}
Images: {img.text}
</div>
)
}
But it showed me
this.props.images.map is not a function.
then I think the problem is with setting state of images (I may be wrong).
When I tried to print this.props.images then it is showing
0: {id: 28, text: '1111', blog_id: 71}
length: 1
[[Prototype]]: Array(0)
I am new in react, Any help would be much Appreciated. Thank You in Advance
this.props.images is an array and hence you can't use {this.props.images} directly. Other wise you will get an error like this "Objects are not valid as a React child. If you meant to render a collection of children, use an array instead"
You have to use something like this
render() {
return (
<div>
Title: {this.props.title} <br/>
Images:
{this.props.images?.map((image, i) => (
<div key={image.id}>
{image.id}<br/>
{image.text}<br/>
{image.blog_id} <br/>
</div>
))}
</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>
);
}

Cannot map subchildren of children

My file structure has a questions array, in which there's one more section_questions array I want to access.
The React code looks like this
<div className="questions-container">
{employee.questions.map(question => (
<p className="section-title">{question.section_name}</p>
/* This won't work, syntax error */
{ question.section_questions.map(sq => ( <p>Yo</p> )) }
)
)}
</div>
JSON-Object:
"questions": [
{
"section_name": "Random",
"section_questions": [
{
"question": "Q1",
"answer": "A1"
},
{
"question": "Q2",
"answer": "A2"
}
]
}
]
Why won't this work? How can I fix this?
The error is Parsing error: Unexpected token, expected ","
The problem seems to be that you need a tag (or fragment) that encloses everything underneath.
So you could:
move the < /p> to be after { question.section_questions.map(sq => ( <p>Yo</p> )) } .
Or enclose everything in a div.
Or use a react fragment < React.Fragment>< React.Fragment/> instead of div. Suggested by: #Antoan Elenkov
See: reactjs.org/docs/fragments.html
Example:
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.questions = [
{ section_name: "Learn JavaScript", section_questions: [1] },
{ section_name: "Learn React", section_questions: [1] },
{ section_name: "Play around in JSFiddle", section_questions: [1] },
{ section_name: "Build something awesome", section_questions: [1] }
];
}
render() {
return (
<div>
<h2>Todos:</h2>
{this.questions.map(question => (
<p className="section-title">
{question.section_name}
{question.section_questions}
{question.section_questions.map(sq => ( <p>Yo</p> ))}
</p>
)
)}
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Fiddle: https://jsfiddle.net/opy9k5hr/
Update
Another option is to enclose everything in a <div> tag.
{this.questions.map(question => (
<div>
<p className="section-title"> {question.section_name} </p>
{question.section_questions}
{question.section_questions.map(sq => ( <p>Yo</p> ))}
</div>
)
)}

Reactjs map function mapping more elements than the ones present in array

I have an array of objects that is like this:
data = [{
"projectId": 1,
"projectName": "Progetto 1",
"clientId": 1,
"projectDescription": "description1",
"projectMailingList": "mailingList1",
"projectCreationDate": "apr 29, 2019",
"projectVersion": 1
},
{
"projectId": 2,
"projectName": "Progetto 2",
"clientId": 1,
"projectDescription": "description2",
"projectMailingList": "mailingList2",
"projectCreationDate": "apr 29, 2019",
"projectVersion": 1
},
{
"projectId": 3,
"projectName": "Progetto 3",
"clientId": 1,
"projectDescription": "description3",
"projectMailingList": "mailingList3",
"projectCreationDate": "apr 29, 2019",
"projectVersion": 1
}];
I use the map function for this array, but it's mapping more elements than the ones present. What I have to do is to render a custom component for each of the element of the array, so that would be 3 times, instead it's creating it 7 times. I've tried doing this inside constructor, to check:
this.data.map(projectId => console.log("Call "));
and it correctly prints "Call" for 3 times. Then, in my render method I do this:
return (
<Slider class="mySlider" ref = {c => (this.slider = c)} {...this.settings}>
{
this.data.map(projectId =>
<ProjectComponent key={projectId} project={projectId} time={this.state.timestamp} originalIndex={ i++ } currentIndex = {this.state.activeSlide}></ProjectComponent>)
}
</Slider>
);
and it creates 7 ProjectComponent s. Why is that? And why 7?
EDIT
This is ProjectComponent:
export class ProjectComponent extends React.Component {
constructor(props) {
super(props);
this.logger = new LoggerService();
this.state = {
project: this.props.project //projects contains the needed data
};
}
componentDidMount() {
// this.logger.info("------ componentDidMount");
if(this.props.originalIndex === this.props.currentIndex) {
this.getProjectData();
}
}
componentDidUpdate(prevProps, prevState) {
if(prevProps.time !== this.props.time
&& this.props.originalIndex === this.props.currentIndex) {
this.getProjectData();
}
}
render() {
var homeService = new HomeService();
if (this.state.isLoading) {
return (
<div className="projectContainer">
<div className="container-fluid">
<div className="row">
<div className="col">
<div className='sweet-loading'>
<ClipLoader
sizeUnit={"px"}
size={100}
color={'#FFECA5'}
loading={this.state.isLoading} />
</div>
</div>
</div>
</div>
</div>
);
} else {
return (
<div className="projectContainer">
<div className="container-fluid">
<div className="row">
<div className="col">
<ProjectHeaderComponent header={this.state.project}></ProjectHeaderComponent>
{<ServiceStatusListComponent functionalities={ homeService.getProjectStatusById(this.props.id)} time={this.props.time}
originalIndex = {this.props.originalIndex} currentIndex = {this.props.currentIndex}></ServiceStatusListComponent>}
</div>
</div>
</div>
</div>
);
}
}
getProjectData() {
this.setState({
project: {},
isLoading: false
});
var homeService = new HomeService();
this.setState({
isLoading:false,
projectResponse: homeService.getProjectStatusById(this.props.id)
});
}
}
Moving
this.data.map(projectId => console.log("Call "));
to render helps when this.data can change in lifecycles. If hardcoded - no difference. If not render is a better place to check state/props right before returning view.
It's better to console.log something more meaningfull, too.
In this case it would help debugging because you're iterating over objects, not reaching real projectId property. The same error occurs within render. Instead of idetifier you're using refs to object - keying fails allowing for <ProjectComponent/> duplicates.
There should be
this.data.map( project =>
<ProjectComponent key={project.projectId} project={project}
The <ProjectComponent /> source was not needed - component can't duplicate itself. That's why its parents needs inspection, f.e. <Slider /> (direct parent) and the 'main' component. If <Slider /> was rendered once then only the second can be a source of error.
As we have <ProjectComponent /> source, we see errors there:
using this.props.id while no id prop passed;
unnecessary creating homeService when condition can lead to render 'loading'

Categories

Resources