Riyesh
Riyesh

Reputation: 83

rendering a nested objected in reactjs

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

Answers (5)

dubes
dubes

Reputation: 5514

If you look at the code, here is the sequence of events:

  1. Component is created, i.e. constructor is called
  2. Component is mounted, i.e. componentDidMount is called
  3. componentDidMount starts an async request to fetch the data which is then parsed and set in state.
  4. 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

Jolly
Jolly

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

Raul Rene
Raul Rene

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

Black Mamba
Black Mamba

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

Murat Karag&#246;z
Murat Karag&#246;z

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

Related Questions