Reputation: 83
I am having a hard time to render a nested object on to a reactjs page
import React, { Component } from "react";
import Toolpanel from "./Todopanel";
import Toollist from "./Toollist";
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
city: "Auckland",
cityWeather: {}
};
this.updateUser = this.updateUser.bind(this);
}
updateUser(entry) {
console.log(entry);
let item = {
text: entry,
key: Date.now()
};
this.setState(prevstate => {
return {
users: prevstate.users.concat(item)
};
});
}
componentDidMount() {
let apiId = "***************************";
let city = this.state.city;
let ApiString =
"http://api.openweathermap.org/data/2.5/weather?q=" +
city +
"&APPID=" +
apiId;
fetch(ApiString)
.then(results => results.json())
.then(data => this.setState({ cityWeather: data }));
}
render() {
let test = this.state.cityWeather;
return (
<div>
<Toolpanel parentUpdate={this.updateUser} />
<div>wind speed : {test.wind.speed} </div>
</div>
);
}
}
export default App;
I have added my JSON file that I received from my weather API
//Json file
{
"coord": { "lon": 174.77, "lat": -36.85 },
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04n"
}
],
"base": "stations",
"main": {
"temp": 293.7,
"pressure": 1018,
"humidity": 77,
"temp_min": 293.15,
"temp_max": 294.26
},
"visibility": 10000,
"wind": { "speed": 5.1, "deg": 360 },
"clouds": { "all": 92 },
"dt": 1553672420,
"sys": {
"type": 1,
"id": 7345,
"message": 0.0043,
"country": "NZ",
"sunrise": 1553624951,
"sunset": 1553667823
},
"id": 2193733,
"name": "Auckland",
"cod": 200
}
I am trying to render the wind speed from the JSON to my page.. but is throwing me a error message saying "TypeError: Cannot read property 'speed' of undefined"...Please help. I am fairly new to ReactJs.
Upvotes: 0
Views: 63
Reputation: 5514
If you look at the code, here is the sequence of events:
constructor
is calledcomponentDidMount
is calledcomponentDidMount
starts an async
request to fetch the data which is then parsed and set in state.render
method tries to read the data from state.Now, since the request in #3 is an async one, it may not have completed in time when the render
method has been called the first time.
So, you need to check if your request has completed or failed or is running.
You can use that to conditionally render the content in your render method.
Recommended reading The official reactjs blog entry on async rendering with examples of when data is fetched from an external resource
Upvotes: 3
Reputation: 1768
Since you didn't post ToolPanel
Component implementation, I may be wrong (I'm missing some information). But, I'm also pretty sure that your problem is not having a loading
variable.
Basically, the first time render()
method is called, you have this.state.cityWeather
to be an empty object {}
; that is because you fetch the data in componentDidMount()
. Thus, the first time render()
is called, being this.state.cityWeather
empty, you cannot access this.state.cityWeather.wind.speed
because you don't have the property wind
in this.state.cityWeather
!
So, usually, the common way to do this is adding a property loading
in the state, and setting it to true
in the constructor
. Then, in the callback of the fetch, while you set the data in this.state.cityWeather
, you also set loading
to true
.
Finally, in the render()
method you wrote a conditional rendering: if this.state.loading === true
, then you print a simple paragraph like <p>I'm retrieving info!</p>
, otherwhise, if this.state.loading === false
, you can render what you want.
Upvotes: 1
Reputation: 10270
You're not wrong the way you approached it. The error you're getting is because the fetch you're performing is taking some time, and render first executes without having the data populated.
So first time it gets in your render method the value of test = {}
. So test.wind.speed
will throw an error.
Instead, show a loading state of some sort or simply return null
until the call is performed:
render() {
let test = this.state.cityWeather;
if (!test) {
return 'Loading...';
}
....
}
Upvotes: 2
Reputation: 15545
Initially your test will be null as you haven't received any response from your API so you should check the variable presence before using it. So just check if it is present before using it like this:
render() {
let test = this.state.cityWeather;
return (
<div>
<Toolpanel parentUpdate={this.updateUser} />
<div>wind speed : {test && test.wind && test.wind.speed ? test.wind.speed : ''} </div>
</div>
);
}
Upvotes: 1
Reputation: 37584
You are accessing the properties too fast since fetch
is an asynchronous call it will take some time but your render
fires before that already.
Use it like this
{ test && <div>wind speed : {test.wind.speed} </div>}
Upvotes: 1