Issues with looping array of objects in react - javascript

I have a json file:
{"places":[{"id":"1","name":"Balzac's Niagara On-The-Lake","logo_url":"http://example.com/store_images/new_82.jpg","website_url":"http://www.example.com","hours":{"monday":"07:00 AM - 07:00 PM","tuesday":"07:00 AM - 07:00 PM"}},{"id":"2","name":"Balzac's Port,"logo_url":"http://example.com/images.jpg","website_url":"http://www.example.com","hours":{"monday":"07:00 AM - 07:00 PM","tuesday":"07:00 AM - 07:00 PM"}}]}
Trying to loop and render the items on browser. Here is my code:
class Dashboard extends Component {
state = {
data: ''
};
componentDidMount() {
axios
.get(url)
.then(resp => resp.data)
.then(data => {
this.setState({ data });
//console.log(data.places[0].id);
console.log(data.places.length);
})
.catch(err => console.log(err));
}
render() {
const { data } = this.state;
let content;
for (let i in data.places) {
console.log(data.places[i].name);
content = <div>{data.places[i].name}</div>;
}
return (
<div className="container">
<h3>{content}</h3>
</div>
);
}
}
I am able to see all the items in the console but not on the UI page.

You have a number of problems here. First, the code is incomplete. So as it this has no hope of working.
Next, you have an array of places not an object, so you cannot use a for-in loop. You'll need to map over those to produce the appropriate markup. See MDN for iterating over various data structures.
Also, do not initialize state.data to an empty string. If you don't strongly type your code, it is good to at least provide an indication of what the type will eventually be. For example here you might supply an empty object ({}) or null. Setting it to an empty string ('') is misleading.
Lastly the JSON you supplied was invalid. Ensure your actual data source is correct. Here that meant correcting a missing double-quote on name property of the second field: "name": "Balzac's Port". There are several online tools for JSON validation.
I've simulated an asynchronous XHR activity here with setTimeout, but the idea is the same:
function simulateXhr() {
return new Promise(resolve => {
setTimeout(() => {
return resolve({
places: [
{
id: "1",
name: "Balzac's Niagara On-The-Lake",
logo_url: "http://example.com/store_images/new_82.jpg",
website_url: "http://www.example.com",
hours: {
monday: "07:00 AM - 07:00 PM",
tuesday: "07:00 AM - 07:00 PM"
}
},
{
id: "2",
name: "Balzac's Port",
logo_url: "http://example.com/images.jpg",
website_url: "http://www.example.com",
hours: {
monday: "07:00 AM - 07:00 PM",
tuesday: "07:00 AM - 07:00 PM"
}
}
]
});
}, 500);
});
}
class Dashboard extends React.Component {
state = {
data: null // or {}
};
componentDidMount() {
simulateXhr().then(data => {
this.setState({ data });
});
}
render() {
return (
<div className="container">
<h3>Places:</h3>
{!this.state.data ? (
"no data yet"
) : (
<ol>
{this.state.data.places.map(place => (
<li>{place.name}</li>
))}
</ol>
)}
</div>
);
}
}
ReactDOM.render(<Dashboard />, 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>

On each iteration of the loop, you're replacing the value of content. That's why on your page it's only displaying the very last item of the array.
In order to display all of the contents of your array, you must iterate over them inside your render().
You can consider using the Array.map method inside of your render method.

Related

JSX select element does not seem to auto-select (implement 'selected') on option. Am I missing something?

I have a <select> tag which I use to create my own custom <SelectField> component as follows:
export default function SelectField(props) {
/*
PARAMS:
- fieldname (String)
- fieldID (String)
- options (Array([Object, Object...]) [{"option_value": "option_name"}, ...])
- init_value (String "option_value")
*/
const generate_options = () => {
// Function for handling SelectField options
let element_list = [];
element_list.push(
<SelectOption key={0} option_value="" option_text="None" />
);
var count = 1;
if (!Array.isArray(props.options)) {
for (var [value, name] of Object.entries(props.options)) {
element_list.push(
<SelectOption key={count} option_value={value} option_text={name} />
);
count += 1;
}
} else {
props.options.forEach((subject) => {
element_list.push(subject.to_option());
});
}
return element_list;
};
const nameToString = () => {
// Converts props.fieldname into a properly formatted name
if(props.fieldname.indexOf("_")){
var full_field_name = `${props.fieldname.split("_").join(" ")}`;
return `${full_field_name[0].toUpperCase() + full_field_name.slice(1)}`;
};
return `${props.fieldname[0].toUpperCase() + props.fieldname.slice(1)}`;
};
return (
<div className="form-group">
<label htmlFor={props.fieldname}>
{nameToString()}:
</label>
<select
name={`${props.fieldname}`}
id={`${props.fieldID}`}
className="countries"
defaultValue={props?.init_value ? `${props.init_value}` : ""}
>
{generate_options()}
</select>
</div>
);
}
(PS. Don't mind the className - it really SHOULDN'T relevant. But who knows with a beginner like me... DS). Now, I want to use that field to create a <select> tag for Subjects within my web-app (powered by Django), and I created a sub-component for it that looks like this:
export function SubjectSelectField(props) {
const [subjects, setSubjects] = useState([]);
useEffect(() => {
const getSubjects = async () => {
let reqObj = new RequestHandler("/courses/subjects/");
const data = await reqObj.sendRequest();
setSubjects(
data.map((item) => {
return new Subject(item);
})
);
};
getSubjects();
}, []);
console.log({ subjects });
return <SelectField options={subjects} {...props} />;
}
When rendering the page with this component, I get the following console.logs:
{
"subjects": [
{
"id": 6,
"name": "Art"
},
{
"id": 4,
"name": "Biology"
},
{
"id": 5,
"name": "Chemistry"
},
{
"id": 3,
"name": "Geography"
},
{
"id": 2,
"name": "Language"
},
{
"id": 1,
"name": "Mathmatics"
},
{
"id": 7,
"name": "Physics"
},
{
"id": 8,
"name": "Social Studies"
}
]
}
For reference; this is my custom Subject class (ES6):
export class Subject {
constructor({ id, name }) {
this.id = id;
this.name = name;
}
to_option() {
return (
<SelectOption
key={this.id}
option_value={this.id}
option_text={this.name}
/>
);
};
}
And the <SelectOption> component:
export function SelectOption(props) {
return <option value={`${props.option_value}`} >{`${props.option_text}`}</option>;
}
So, the output I expect is for the <SelectField> to automatically assign the selected attribute to the option that has the value of the <SelectField>'s init_value prop.
I use the <SelectField> for another type of field I call <CountrySelectField> and that works just fine. The country I expect to be pre-selected is properly selected as per its init_value.
That component looks like this:
export function CountrySelectField(props) {
return (
<SelectField
init_value={props?.student?.country ? `${props.student.country}` : ""}
{...props}
/>
);
}
As I mentioned, if I pass a value to this component's init_value prop, it executes just as expected and renders with correct option having selected attribute set on it.
The <SubjectSelectField> doesn't render the way I expect - with correct <option> having a selected attribute set.
Why does it not work?
EDIT:
Changed the Subject class to a React functional component:
export function Subject(props){
const id = props.subject.id;
const name = props.subject.name;
console.log(id, name, props.subject);
if(props.option){
return(
<SelectOption
key={id}
option_value={id}
option_text={name}
/>
);
}
return(
<h1>{name} - {id}</h1>
);
}
In order for other things to work, I changed these things too:
export function SubjectSelectField(props) {
// ...
setSubjects(
data.map((item) => {
return <Subject subject={item} />;
})
);
// ...
The option still won't be auto-selected.. I have other fields that works with mostly the same logic (SelectField is used in other places as well and works), but I have clearly messed something up here.
PS.
If you have time to language-police, you definitely have time to look into the question too. Don't you, #halfer? Jeez..
DS.
I am relieved to have solved this problem on my own, and the problem was that I never implemented a check on the SubjectSelecField to render the <select> field ONLY when the Subjects are loaded form the server.
E.g. like this:
export function SubjectSelectField(props) {
const [subjects, setSubjects] = useState([]);
// Here I use 'loaded' as a check variable when rendering.
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const getSubjects = async () => {
let reqObj = new RequestHandler("/courses/subjects/");
try{
const data = await reqObj.sendRequest();
setSubjects(
data.map((item) => {
return <Subject subject={item} />;
})
);
// If the Subjects load properly, set 'loaded' to true.
setLoaded(true);
}
catch(error){
console.log(`Something went wrong when trying load Subjects from server!`);
console.error(error);
// Else we make sure its set to false.
setLoaded(false);
}
};
getSubjects();
}, []);
console.log({ subjects });
// Here, before rendering, we check for the 'loaded' variable
// to make sure the <select> tag doesn't try to load without
// the <option> tags. Otherwise, the 'defaultValue' prop won't work.
if(loaded) return <SelectField init_value={props.init_value} options={subjects} {...props} />;
return <span>Loading Subject list...</span>;
}
Thank you people for trying to look into it at least :)

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 :)

How to access my state of array in another router page VUEJS, VUEX

I made a page with two routes one is the home page another is the config where you can decide what should be written to that container, now in the config panel I was able to get the input values, I put them to my state with map actions now I am getting an array with string values in it.
How can I access that array with mapGetters ? I link my code:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{ message }}</h1>
</div>
</body>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
export default {
name: "Home",
data() {
return {
// message: this.store.state.message
elementVisible: true
};
},
computed: {
...mapGetters(["message", "sec"]),
...mapGetters({
message: "message",
sec: "sec"
}),
createdDate() {
return moment().format("DD-MM-YYYY ");
},
createdHours() {
return moment().format("HH:mm ");
}
},
mounted() {
this.$store.dispatch("SET_MESSAGE");
this.$store.dispatch("SET_SEC");
setTimeout(() => (this.elementVisible = false), this.sec);
}
};
</script>
so what I have to do is to put to that{{message}} template my message which I received from the config panel and which is in my state right now sitting there as an array of string, for example, ["hello", "how are you"] that's how they are sitting there, so how can I grab the first one('hello') and write it out as a clean string and not as ["hello"] if you know how to grab all of them would be even better.
(RightNow it's just putting that whole array to my template)
Maybe I should something rewrite in my storejs file?
STOREJS:
const state = {
message: [],
// console.log(message);
sec: +[]
// other state
};
const getters = {
message: state => {
// console.log(this.state.message);
return state.message;
},
sec: state => {
return state.sec;
}
// other getters
};
const actions = {
setMessage: ({ commit, state }, inputs) => {
commit(
"SET_MESSAGE",
inputs.map(input => input.message)
);
return state.message;
},
setSec: ({ commit, state }, inputs) => {
commit("SET_TIMEOUT", inputs.map(x => x.sec).map(Number));
console.log(inputs.map(x => x.sec).map(Number));
return state.sec;
}
// other actions
};
const mutations = {
SET_MESSAGE: (state, newValue) => {
state.message = newValue;
},
SET_TIMEOUT: (state, newSecVal) => {
state.sec = newSecVal;
}
// other mutations
};
export default {
state,
getters,
actions,
mutations
};
what my homepage should do is that it writes out that message and there is a sec value which stands for the timeout, after that I want to continue with the second value in that array and when that times out I should want the third to be written out.. and so on.
Thank you!
Hello and welcome to Stack Overflow! Your message Array is being mapped correctly with mapGetters, but you're flattening it as a String when you put it inside the template with {{message}}, since the template interpolation logic covert objects to strings, the same as calling Array.toString in this case. You need to iterate it, i.e. using the v-for directive:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">
<span v-for="m of message" :key="m">{{m}}</span>
</h1>
</div>
</body>
</template>
Of course, if you only need the first item, you could show it directly using:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{message[0] || 'No message'}}</h1>
</div>
</body>
</template>

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'

Mapping an array inside an object in React with ES6

I'm making a react-feed app, inspired by Michael's tutorial (there's also a video) and I encountered some trouble trying to pass an array inside an array as props using Lodash's _.map function. This is the information I'm mapping:
const events = [
{
event: 'Gods',
venue: 'Asgard',
venuePicture: 'picture1.jpg',
when: '21:00 27/04/16',
genres: ['rock', 'funk'],
artists: [
{
artist: 'Thor',
artistPicture: 'thor.jpg'
},
{
artist: 'Loki',
artistPicture: 'loki.jpg'
}
]
},
{
event: 'Humans',
venue: 'Midgard',
venuePicture: 'picture2.jpg',
when: '21:00 27/04/16',
genres: ['jazz', 'pop'],
artists: [
{
artist: 'Human1',
artistPicture: 'human1.jpg'
},
{
artist: 'Human2',
artistPicture: 'human2.jpg'
}
]
}
];
I'm passing to the component like this (this works):
renderItems(){
const props = _.omit(this.props, 'events');
return _.map(this.props.events, (event, index) => <EventsFeedItem key={index} {...event} {...props}/>);
}
render() {
return (
<section>
{this.renderItems()}
</section>
);
}
This works perfectly fine, dividing each "event" object
Then I try to destructure and map the "artists:" object of each event like this:
renderArtists() {
const { event, venue, venuePicture, when, genres, artists } = this.props.events;
const props = _.omit(this.props, 'events');
return _.map({artists}, (artist, index) => <ItemArtist key={index} {...artist} {...props}/>);
}
render() {
return (
<ul>
{this.renderArtists()}
</ul>
);
}
This is the result I'm getting, which is close, but not what I need:
I need to separate these further to get:
{artist: "Thor"} {artistPicture: "thor.jpg"}
{artist: "Loki"} {artistPicture: "loki.jpg"}
and so on...
I see there's a pattern here, I just don't know how to implement it further. It breaks when I try to repeat the same destructure then _.map thing. Can anyone please give me a hand with this, sorry for the long post.
return _(this.props.events).flatMap('artists').map((artist, index)=><ItemArtist key={index} {...artist} {...props}/>).value();
Oh, I found the problem thanks to VyvIT's comment (he deleted his comment), it's here:
const { event, venue, venuePicture, when, genres, artists } = this.props.events;
_.map({artists}, (artist, index) => <ItemArtist key={index} {...artist} {...props}/>);
"artists" shouldn't be destructured (curly brackets), should be like this:
_.map(artists, (artist, index) => <ItemArtist key={index} {...artist} {...props}/>);
Thank you so much guys!

Categories

Resources