React cannot read an object inside array (state) - reactjs

Relatively new to React, wrote a few components before (successfully), but this is the first time I'm getting something started from scratch. I use Rails with Cassandra to provide JSON responses, and everything on the back end side is working fine.
My data this.state.data looks like this:
0 {id: {n: 2.1751612473052575e+38}, email: "123#zeba.me", high_score: 73, shoe_size: 10.5, updated_at: "2018-11-06T01:23:36.611-08:00"}
1 {id: {n: 2.8024982600468778e+38}, email: "123#sotkamonlukio.fi", high_score: 13, shoe_size: 7.5, updated_at: "2018-11-06T01:24:55.791-08:00"}
2 {id: {n: 2.4227336868283995e+38}, email: "123#icloud.com", high_score: 99, shoe_size: 10.5, updated_at: "2018-11-06T01:24:07.858-08:00"}
And doing this.state.data[1] obviously returns
{id: {n: 2.8024982600468778e+38}, email: "123#sotkamonlukio.fi", high_score: 13, shoe_size: 7.5, updated_at: "2018-11-06T01:24:55.791-08:00"}
However, this.state.data[1].email throws this
TypeError: undefined is not an object (evaluating 'this.state.data[1].email')
What can I do to access email and the rest of the data?
full component code:
import React, { Component } from 'react';
export default class Table extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
componentDidMount() {
fetch('http://localhost:3000/api/users')
.then(response => response.json())
.then(data => this.setState({ data }))
}
render() {
return(
<div className="table">
<h1>LOL</h1>
<p>{this.state.data[1].email}</p>
</div>
)
}
}

The problem is that, when your component starts the rendering, the this.state.data[1].email wasn't loaded already. Just check if the data was already loaded, like below:
render(){
if(this.state.data.length > 0)
return(
<div className="table">
<h1>LOL</h1>
<p>{this.state.data[1].email}</p>
</div>
)
else return <div/>
}
The other possible solution is to use the map function. It will only render something when the array it's filled. The code is below:
render(){
return(
<div className="table">
<h1>LOL</h1>
{ this.state.data.map(user => <p>{user.email}</p>) }
</div>
)
}
For each user in the data array, the component will render a tag <p> with the email. When there's nothing in the array, the component will render nothing.

its a common error the request didn't complete before the render method is called just add an if statement
render () {
const { data } = this.state
return (
<div className='table'>
<h1>LOL</h1>
{data.length > 0 && <p>{data[1].email}</p>}
</div>
)
}

This.state.data initially set to an empty array. So when your component first time renders it will be an empty array.
In componentDidMount you are making an api call and assigning api response to the state data. So this method gets called after first render
The reason you get that issue because you are accessing 1st index from this.state.data array which is obviously an empty array at the time of first render. So what you have to do is
Change
<p>{this.state.data[1].email}</p>
To
<p>{this.state.data.length > 0 && this.state.data[1].email}</p>
Or do iterate the array and render p elements
{this.state.data.length > 0 && this.state.data.map(d => (
<p key={d.id}>d.email}</p>
)}
If you don’t have unique id per object in array then use index as key

Related

Accessing realtime firebase object in react.js

Currently trying to access the value within our Joblist object. We are unable to do joblist[0].company which should return google.
What is the best way to access the values of our object?
Our joblist object contains:
key: value
company: "Google"
datePosted: "2021-09-20T05:00:00.000Z"
location: "San Francisco"
position: "SWE Intern"
[{
"company" : "Google",
"datePosted" : "2021-09-20T05:00:00.000Z",
"id" : 0,
"location" :
"San Francisco",
"position" : "SWE Intern"
}]
App.js code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {joblist : []}
}
componentDidMount() {
firebase.database().ref("1h5GOL1WIfNEOtcxJVFQ0x_bgJxsPN5zJgVJOePmgJOY/Jobs").on("value", snapshot => {
let jobs = [];
snapshot.forEach(snap => {
// snap.val() is the dictionary with all your keys/values from the 'students-list' path
jobs.push(snap.val());
});
this.setState({ joblist: jobs });
});
}
render(){
const {joblist} = this.state;
// console.log(joblist[0].datePosted)
console.log(joblist);
return (
<div id='body'>
<Header />
<PhoneInput />
<TableHeader />
<Table joblist = {joblist}/>
<Footer />
</div>
);
}
}
export default App;
The one thing that jumps out to me is the commented out console.log(joblist[0].datePosted), which will give an error initially as the array will still be empty. To catch that you'll need to do:
if (joblist.length > 0) console.log(joblist[0].datePosted);
When you define your joblist, make sure that it is declared and defined as an empty array when your app initiates. this would prevent any issues of pushing items to it through the state manager.
Console logging the whole object from the snapshot val() may also yield inside to the object since Arrays are not natively supported, this could be the case if you are using UID's for each entry but are not unpacking the data first.

Can't render an array inside of the ReactJS state

I'm fetching the data from an external API for food recipes, and getting the response in this format (JSON):
{
"count": 30,
"recipes": [
{
"publisher": "BBC Food",
"f2f_url": "http://food2fork.com/view/8c0314",
"title": "Chicken and cashew nut stir-fry",
"source_url": "http://www.bbc.co.uk/food/recipes/chickenandcashewnuts_89299",
"recipe_id": "8c0314",
"image_url": "http://static.food2fork.com/chickenandcashewnuts_89299_16x9986b.jpg",
"social_rank": 95.91061636245128,
"publisher_url": "http://www.bbc.co.uk/food"
},
{
"publisher": "Jamie Oliver",
"f2f_url": "http://food2fork.com/view/0beb06",
"title": "Roasted chicken breast with pancetta, leeks & thyme",
"source_url": "http://www.jamieoliver.com/recipes/chicken-recipes/roasted-chicken-breast-with-pancetta-leeks-and-thyme",
"recipe_id": "0beb06",
"image_url": "http://static.food2fork.com/466_1_1349094314_lrg2129.jpg",
"social_rank": 94.88568903341375,
"publisher_url": "http://www.jamieoliver.com"
},
{ ... more recipes ... }
]
}
And I'm trying to access that data and display, for the purpose of testing, just variables 'count', and the 'publisher' of the first recipe in array. This is my React code:
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: {} };
}
componentDidMount() {
fetch('https://www.food2fork.com/api/search?key=MY_KEY&q=chicken%20breast&page=2')
.then(response => {
return response.json();
})
.then(jsonData => {
this.setState({ data: jsonData }, function() {
console.log(jsonData);
});
});
}
render() {
return (
<div className="App">
<h1>{this.state.data.count}</h1>
<p>{this.state.data.recipes[0].publisher}</p> // Why this doesn't work?
</div>
);
}
};
If I remove the 'p' tag in the render() function, everything works as expected: the page loads at first, and then after fetching the data, displays '30' as 'h1'.
However, if I run the code with the 'p' tag, I get this error:
I'm searching for the answers for more than two hours and really can't find the answer. Why can I access the variable 'count', but not the variable 'publisher', which is inside of an array? I'm event logging out this.state after setting it, and object looks completely normal there. How can I access the elements in the 'recipes' array?
This is is because when you are fetching data at that time react render the component and you got error as this.state.data is still {} so this.state.data.recipes[0] is yet not defined as fetch request is not completed (it take some time). To resolve it you have to return on 2 conditions.
when fetch is running (Loading)
2) when fetch is completed
render() {
if(!this.state.data.recipes){
// if the fetch request is still not completed
return (
<div>
<h1>Loading .... </h1>
</div>
)
}
// run when fetch request is completed and this.state.data is now assigned some data
return (
<div className="App">
<h1>{this.state.data.count}</h1>
<p>{this.state.data.recipes[0].publisher}</p> // Why this doesn't work?
</div>
);
}
answer for your comment.
the error is can not read property 0 of undefined which means this.state.data.recipes is undefined and thus.state.data.recipes[0] is error right?.
But when you use this.state.data.count then you did not get error. As it will print undefined that is value of count (at that moment and you are not trying to print further like this.state.data.count.toString() if you do so it will through you error can not read property toString() of undefined).
and in then() when you use this.setState() it will update the state and react will re-render all affected components.

React Passing props to another looped Component

I'm trying to pass props of looped component(using array.map()) to other component, made as follows:
{this.posts.map((item, index) => {
return (
<Post
item={item}
key = {index}
/>
);
})
}
So there are many 'Post' sibling components being rendered, but each with different items(data) and keys. Now I wish to send the props(to share a state between the two components) of one specific Post component to another specific sibling Post component. That is, I wish to select a specific Post component (maybe with the key value? and send a state only to another specific post component).
How would I achieve this?
Thank you.
I would recommend binding a function that would update this.posts. That way your components will all update without having to take on potentially entangled states.
The idea is the function updatePosts will take a target index (key) child component to update and pass it the new state. In the code below, if you triggered updatePosts({firstName: 'Harry', lastName: 'Styles'}, 1), then the state would change from the first element being 'Foo Bar' to being 'Harry Styles', and the child components will rerender.
import React from 'react';
class PostMaster extends React.Component {
constructor(props) {
super(props);
this.state = {
//the array you use to render child components will be managed in a state like this
data: [
{
firstName: 'Foo',
lastName: 'Bar'
},
{
firstName: 'James',
lastName: 'Bond'
},
{
firstName: 'Harry',
lastName: 'Potter'
}
]
};
this.updatePosts = this.updatePosts.bind(this);
};
/**
* stateData (object) the data you want to go in a given index
* index (int) the child component you would like to update
/
updatePosts(stateData, index) {
let newData = this.state.data;
newData[index] = stateData;
this.setState({data: newData});
//you can always push instead of reassigning
};
render() {
return(
<div>
{this.state.data((item, index) => {
return (
<Post
item={item}
key={index}
update={this.updatePosts}
/>
);
})}
</div>
)
}
}

React.js, pulling data from api and then looping through to display

Im new to react. I am trying to pull data from an API, and then loop through it and display it.
Error : Cannot read property 'map' of undefined.
The API data is coming through, but it seems as if React is calling the looplistings before the data is stored into State.
constructor () {
super()
this.state = {
data:'',
}
}
componentWillMount(){
// Im using axios here to get the info, confirmed data coming in.
//Updating 'data' state to equal the response data from the api call.
}
loopListings = () => {
return this.state.data.hits.map((item, i) => {
return(<div className="item-container" key={i}>
<div className="item-image"></div>
<div className="item-details">tssss</div>
</div>)
})
}
loopListings = () => {
return this.state.data.hits.map((item, i) => {
return(
<div className="item-container" key={i}>
<div className="item-image"></div>
<div className="item-details">tssss</div>
</div>)
})
}
render () {
return (
<div>
{this.loopListings()}
</div>
)
}
The reason you are receiving this error is that your call to the API is happening asynchronously to the react lifecycle methods. By the time the API response returned and persisted into the state the render method has been called for the first time and failed due to the fact you were trying to access an attribute on a yet undefined object.
In order to solve this, you need to make sure that until the API response has been persisted into the state the render method will not try to access that part of the state in your render method or to make sure that if it does there is a valid default state in the constructor:
Solve this by changing your render to do something like this:
render () {
return (
<div>
{this.state.data &&
Array.isArray(this.state.data.hits)
&& this.loopListings()}
</div>
)
}
or initialize your constructor like so :
constructor () {
super()
this.state = {
data: {hits: []},
}
}
Remeber react is just javascript and its behavior is just the same.
You could check if desir data.hits exists inside state.
{this.state.data && Array.isArray(this.state.data.hits) ?
this.loopListings()
: null}
Also make sure that, after retrieving a data cal this.setState method like below.
this.setState({ data })

How to render properties of an Array of objects in React?

So, I have the exact same problem as our friend here :
How to render properties of objects in React?
The below (upvoted) solution by Nahush Farkande :
render() {
let user = this.props.user || {};
....
{user.email}
....
}
works for me... if user is an object. However, in my specific case, the data I fetch and want to render is an array of objects.
So, I return something like that :
<ul>
{
user.map( (el, idx) => {
return (
<li key = {idx}>
<div className="panel-body clearfix">
{el.title}
</div>
</li>
)
})
}
</ul>
That doesn't work. I get an error message that tells me that user.map is not a function (before [HMR] connected).
I expected that once the API fetches the user array of objects, the component would re-render and then the component would show the list of titles from each object of the user array (after [HMR] connected).
If your user (I recommend to rename to users) is an array, then you cannot use {} as the default. You should use [] as the default value:
const user = this.props.user || []
or, you can use a completely different branch to handle the loading case:
if (!this.props.user) {
return (
<div> ... my loading placeholder ... </div>
);
}
You already have correct answer but just wanted to give a running example.
Initialize your data in your state with the default values e.g
in case of object -> {}
in case or array -> []
Obviously in each case your rendering logic should be different e.g in case of array you need the map to loop over array and generate jsx element.
So when ever your component receives the updated data ( either it can be empty data or complete data) either via api call or via prop changes use the appropriate life cycle method such as componentWillReceiveProps or componentDidMount to get the latest data and again set the state with latest data.
For example when data is received via api call ->
constructor() {
this.state = {
data : []
}
}
componentDidMount()
{
this.getFunction();
}
getFunction = () => {
this.ApiCall()
.then(
function(data){
console.log(data);
// set the state here
this.setState({data:data});
},
function(error){
console.log(error);
}
);
}
So at the time of initial render your data will be either empty object or empty array and you will call appropriate rendering method for that accordingly.
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
data : []
}
}
componentWillMount() {
this.setState({ data : this.props.dp });
}
renderFromProps() {
return this.state.data
.map((dpElem) =>
<h3>{dpElem.name}</h3>
);
}
render() {
return (
<div>
<h1> Rendering Array data </h1>
<hr/>
{ this.renderFromProps()}
</div>
);
}
}
const dynamicProps = [{ name:"Test1", type:"String", value:"Hi1" },
{ name:"Test2", type:"String", value:"Hi2" },
{ name:"Test3", type:"String", value:"Hi3" }
];
ReactDOM.render(
<Test dp={dynamicProps} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
</div>

Resources