Can't get weather info to display (React)

I can’t seem to get the data I extracted from the Open Weather API to display on my web page. My idea was to create a WeatherCard component that would house both the CurrentWeather and Forecast components. Right now, I’m taking small steps and just trying to display the data for CurrentWeather.

WeatherCard

import React from 'react';
import CurrentWeather from "./Current_Weather";
import Forecast from "./Forecast";

export default class WeatherCard extends React.Component {
  render() {
    const {
      data
    } = this.props
    return (
      <CurrentWeather
        city={data.city}
        temp={data.temp}
      />
    )
  }
}


CurrentWeather

import React from 'react';

export default class CurrentWeather extends React.Component {
  render() {
    const {
      city,
      temp,
      hiTemp,
      lowTemp,
      description,
      icon
    } = this.props;
    return (
      <div className="currentWeatherCard">
        <div className="city">
          <h3>{city}</h3>
        </div>
        <div className="currentTemp">
          <h4>{temp}</h4>
        </div>
      </div>
    )
  }
}


And then here, in the main App component, I’m trying to display the data:

  this.state = {
    search_query: '',
    data: {
      currentWeatherData: null,
      forecastData: null,
    },
    loading: false
  }
  ...
  ...
  render() {
    return (
      <div className="main-wrapper">
        <header id="header_text"><strong>Weather App</strong></header>
        <Search_Bar
          onChange={(event) => this.handleChange(event)}
          search_query={this.state.search_query}
          onClick={() => this.handleClick()}
        />
        <div className="results">
          <WeatherCard data={this.state.data.currentWeatherData}/>
        </div>
      </div>
    )
  }
}

But then I get a TypeError that claims the city prop of the CurrentWeather was undefined. I’m just confused here because I thought I did, in fact, define it:

    const {
      city,
      temp,
      hiTemp,
      lowTemp,
      description,
      icon
    } = this.props;

Is there something I’m missing here?

The code that updates the currentWeatherData is through a function called fetchCurrentWeather(search_query); it’s stored in a file called fetchCurrentWeather.js. I imported the function in App.js and it is invoked in the handleClick() function.

Here is the full project code on GitHub.

Hi.
There could be a problem with fetchCurrentWeather. In the line

return fetch(OPEN_WEATHER_MAP_URL)
   .then(res => res.json())
   .then(extractData)
   .then((data) => {console.log(data)})
   .catch(console.log)

the last promise resolves to a function that prints to the console. So this is what gets returned, I think, not the actual data. Maybe a better way would be

fetch(OPEN_WEATHER_MAP_URL)
   .then(res => res.json())
   .then(data => {
      console.log(data); 
      return data;
   }).catch(err => console.log(err))

The extract data function you chained in there isn’t really necessary, I think. If there would be no response from the res.json() the catch in the end would log the problem.

Hi @wutzig! I tried out your suggestion and I realized I should be returning the data as well.

I have it there because for the sake of this project, I’m only interested in a few data points in the returned JSON. So I’m extracting out things like the city and the temp.

I have it there because for the sake of this project

Yeah, I do that in my service layers too, often. I often trim and transform the data there to fit my needs.

I went ahead and corrected that and pushed the changes. I believe I’m using a valid key because I’m still getting data returned:

However, I’m still not seeing any changes happening on the actual DOM (no header element appearing with the name of the city; this.state.data.currentWeatherData doesn’t seem to be updating correctly)

Could it have something to do with the fact that I’m trying to update an inner object?

this.state = {
  search_query: '',
  data: {
    currentWeatherData: {},
    forecastData: {},
  },
  loading: false
}

using this code to update?:

  handleClick() {
    const {search_query} = this.state;
    if(!search_query) return;
    this.setState({loading: true, });
    fetchCurrentWeather(search_query)
      .then((currentWeatherData) => {
        this.setState({currentWeatherData,})
      })
    fetchForecast(search_query)
      .then((forecastData) => {
        this.setState({forecastData, loading: false})
      })

  }

In your fetchForecast there seems to be the same issue with the return fetch statement. So the last setState in handleClick doesn’t receive any data.

Ok so I think I found a solution. As I suspected, the issue may have been from me not properly setting the state of the nested object currentWeatherData. I went ahead and changed this piece of code in handleClick() a little bit:

    fetchCurrentWeather(search_query)
      .then((currentWeatherData) => {
        this.setState({data:{currentWeatherData,}})
      })

And then, in render/return, I changed the data attribute of the WeatherCard component to this.state.date so that I can then properly access currentWeatherData (along with forecastData.

I went ahead and refactored fetchForecast and am now returning the data. But now I’m having trouble with updating the state of the forecastData. This issue seems similar to the issue I had with updating the currentWeatherData (which is updating correctly).

And this is the code that produced those logs (taking note of lines 39 and 45, which shows the state of currentWeatherData and forecastData, respectively).

    fetchCurrentWeather(search_query)
      .then((currentWeatherData) => {
        this.setState({data:{currentWeatherData,}})
        console.log(this.state.data.currentWeatherData); <---- App.js 38
        console.log(this.state.loading); 
      })
    fetchForecast(search_query)
      .then((forecastData) => {
        this.setState({forecastData, loading: false}) <--- More on this below
        console.log(this.state.data.forecastData); <---- App.js 45
        console.log(this.state.loading);
      })

I then refactored the .then() chain under fetchForecast() to be similar to how the currentWeatherData was updated (again, it’s updating correctly):

    fetchForecast(search_query)
      .then((forecastData) => {
        this.setState({data:{forecastData,}, loading: false})
        console.log(this.state.data.forecastData);
        console.log(this.state.loading);
      })

But am now getting this TypeError when I click the search button:

I can’t seem to figure out why, when coded almost identically, the currentWeatherData updates but the forecastData doesn’t.

You are overwriting the data object at each setState calls: remeber that setState perform a shallow merge.

When you write this:

 this.setState({data:{forecastData,}

means that after state is updated, state.data will only have a forecastData properties inside, unless you copy over all the property it had.

I made a quick sandbox where you can see this behaviour:
You have two props nested: a and b, ad two different setState calls.
You can see how on toggleA the program updates a and “remove” any reference to b,
On the other hand on toggleB you can see the data being updated and kept :slight_smile:

And an easy workaround.
Hope this will help :+1:

This is a common problem with React components that get async data - How do you handle the component before the data arrives? Remember that React will try to render before the data arrives, unless you try to stop it in some way.

But first the problem. The error message is saying “TypeError: Cannot read property ‘city’ of undefined”. Trust me, as a React developer, I see that a lot. You are trying to access data.currentWeatherData.city. It is saying that the object on which you are trying to read “city” us undefined. That is, data.currentWeatherData is undefined, specifically the second half. There are ways to handle this. First of all, I probably would have just sent data.currentWeatherData to the child component and let it split it up. And in that component, I might have a first line like:

if (!currentWeatherData) {
  return null;
}

That is called a “fast fail”. I realizes it doesn’t have the data it needs so it exits the function. It returns null and React is smart enough to render nothing. Then, when the data appears, everything will render correctly.

Another solution is to give it safe data. For example:

<CurrentWeather
  city={ data.currentWeather ? data.currentWeather.data : '' }

In other words, if data.currentWeather is falsy (e.g., undefined) it gives it a blank screen. This could be handled here, in the child component, in a service level, etc. There are also function like lodash’s get that can help.

There are other ways to handle things like this by using spinners or skeleton loaders. You would have to know that the fetch is running, either by incomplete data or with a flag.

There are a lot of ways to handle this - these are a few. These are just the kinds of tricks that you pick up as you right code. Give some of them a try.

Hi Kevin!

Thank you for reaching out!

Does this mean that a WeatherCard isn’t really necessary? I would just pass the state data directly to the CurrentWeather and Forecast components?

I had a question about your second suggestion.

When defining the prop of city in CurrentWeather, why is it written like this?

city={ data.currentWeather ? data.currentWeather.data: ' ' }

If the prop is called city, wouldn’t data.currentWeather.city be the appropriate assignment instead of the currentWeather object?

Does this mean that a WeatherCard isn’t really necessary?

Just looking briefly, I would question why there are two components there. Adding components is good, but if they do something. It seems like this is just passing the data through. Unless you have future plans for it.

If the prop is called city , wouldn’t data.currentWeather.city

Yes, you are right, I made a typo, good catch.

Hi @Marmiz!

Thank you for the example you shared! I tried it out and I now realize what you were saying about overwriting data. Is this possible to do with one button and not two?

I wanted to have two components that make two different fetches from the Open Weather API. One component displays fetched current weather data and the other displays fetched forecast data. That’s why I’m starting to feel that WeatherCard is a useless wrapper component.

I think it would make sense to have a reusable WeatherDisplay card that gets used for each set of data, if I understand you.

My thoughts for the WeatherCard were that it would display two sets of data passed as props for two components, CurrentWeather and Forecast. Ideally, the WeatherCard would display the current data and the 5-day forecast data (day, hi/low, etc).

True. But one possibility would be to reuse the weather formatting for each day, but that may not be what is wanted.

I’m sorry but I’m not following. What do you mean by

?

This is how I would like CurrentWeather and Forecast to be displayed:

I noticed I never included a WeatherCard in the design draft, but this is ideally how the data would be formatted and displayed.

So I think @Marmiz was right in that setState() only does a shallow merge.

    this.state = {
      search_query: '',
      data: {
        currentWeatherData: {},
        forecastData: {},
      },
      loading: false
    }

Because data is a nested object (which contains two more nested objects), using this.setState() as usual causes the structure of data to be overwritten (which causes the TypeError).

When I add the currentWeatherData in the first fetch-chain, my state looks like this:

this.state = {
  search_query: `Pittsburgh`, 
  data: {
    currentWeatherData:  {city: "Pittsburgh", temp: 273.5, hiTemp: 275.37, lowTemp: 272.04, description: "overcast clouds", …},
  }
  loading: false,
}

So now forecastData has disappeared because of the shallow merge! Hence, why data.forecastData.city is undefined.

This article helped me better understand how this.setState() affects the actual structure of state.

So I guess my question is: How do you “deep merge” and make it so the original structure of my state is intact and nothing disappears?