Cannot read property 'temp' of undefined

As a side project to learn React I chose to make a weather app. For this I went with the OpenWeatherMap API and chose the One Call API and the Geocoding API. The problem I have is that when I want to render the JSON responses I get an error: TypeError: Cannot read property 'temp' of undefined. On their documentation (OpenWeatherMap One Call API Docs) it seems that the object I need to use for temperature is current.temp but it doesn't work.

Forecast.js

function getForecast(e) {
    e.preventDefault();

    if (city.length === 0) {
        return setError(true);
    }

    // Clear state in preparation for new data
    setError(false);
    setResponseObj({});
    setLoading(true);

    const uriEncodedCity = encodeURIComponent(city);
    let KEY = process.env.REACT_APP_API_KEY
    const geoCoding = fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${uriEncodedCity}&limit=1&appid=${KEY}`, {
        "method": "GET"
    })
        .then(responseGeo => responseGeo.json())

    geoCoding.then(apiCall => {
        return fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${apiCall[0].lat}&lon=${apiCall[0].lon}&units=${unit}&appid=${KEY}`)
    })
        .then(response => response.json())
        .then(response => {
            setResponseObj(response);
            setLoading(false);
        })
        .catch(err => {
            setError(true);
            setLoading(false);
            console.log(err.message);
        });
}

Conditions.js

const conditions = (props) => {
    return (
        <div className={classes.Wrapper}>
            {
                props.error && <small className={classes.Small} > Please enter a valid city. </small>}
            {
                props.loading && < div className={classes.Loader} />}

            {
                <div className={classes.information}>
                    {/* <h1 className={classes.location}><strong>{props.responseObj.name}, {props.responseObj.sys.country} </strong></h1>
                        <p className={classes.title} ><img className="card-img-top" src={`http://openweathermap.org/img/wn/${props.responseObj.weather[0].icon}@2x.png`} alt="weather icon" style={{ width: 130, height: 130 }} /></p> */}
                    <p className={classes.title} >{Math.round(props.responseObj.current.temp)}° with {props.responseObj.current.weather[0].description}. </p>
                    {<p className={classes.info}>{clothes(props.unit, Math.round(props.responseObj.current.temp))}</p>}
                    <p className={classes.info}>{rain(props.responseObj.current.weather[0].description)}</p>
                    <p className={classes.info}><img src={Thermo} alt="feels like icon" style={{ width: 50, height: 50 }} /> Feels like: <b>{(props.responseObj.current.feels_like).toFixed()}° </b></p>
                    <p className={classes.info}><img src={Atmoshperic} alt="atmospheric icon" style={{ width: 50, marginRight: 5, height: 50 }} /> Atmoshperic Pressure: <b>{props.responseObj.current.pressure}hPa</b></p>
                    <p className={classes.info}><img src={Humidity} alt="humidity icon" style={{ width: 50, marginRight: 5, height: 50 }} /> Humidity: <b>{props.responseObj.current.humidity}%</b></p>
                    <p className={classes.info}><img src={Cloudy} alt="cloudy icon" style={{ width: 50, height: 50 }} /> Cloudiness: <b>{(props.responseObj.current.clouds)}% </b></p>
                    <p className={classes.info}><img src={Wind} alt="wind icon" style={{ width: 50, marginRight: 5, height: 50 }} /> Wind: <b>{props.responseObj.current.wind_speed} m/s, {compassSector[(props.responseObj.current.wind_deg / 22.5).toFixed(0)]} </b> <img src={Arrow} alt="windarrow" style={{ width: 14, transform: `rotate(${props.responseObj.current.wind_deg}deg)`, height: 14 }} /></p>
                    <p className={classes.info}><img src={Sunrise} alt="sunrise icon" style={{ width: 50, marginRight: 5, height: 50 }} /> Sunrise is at: <b>{moment.unix(props.responseObj.current.sunrise).format('HH:mm')} </b></p>
                    <p className={classes.info}> <img src={Sunset} alt="sunset icon" style={{ width: 50, marginRight: 5, height: 50 }} />Sunset is at: <b>{moment.unix(props.responseObj.current.sunset).format('HH:mm')}</b></p>
                </div >
            } </div>
    )
}

Edit:

The thing that I don't understand is why some of the fields from the JSON response can be rendered without any problems and some can't.

Example:

{
   "lat":47.1667,
   "lon":27.6,
   "timezone":"Europe/Bucharest",
   "timezone_offset":10800,
   "current":{
      "dt":1627819113,
      "sunrise":1627786102,
      "sunset":1627839801,
      "temp":86.52,
      ...
}

If I use props.responseObj.lat or props.responseObj.lon I get no error, but if I try to use props.responseObj.current.temp I get the TypeError: Cannot read property 'temp' of undefined error.

Upvotes: 1

Views: 541

Answers (1)

Drew Reese
Drew Reese

Reputation: 202864

Issue

It looks like you are accessing into an undefined property on the initial render.

  1. In Forecast the initial responseObj state is an empty object and loading state is initially false.
  2. {} responseObj value is passed to Conditions.
  3. Conditions attempts to access props.responseObj.current.XXXX.

props is OFC defined, and so is props.responseObj, the value being {}. The next segment props.responseObj.current is undefined. This alone isn't an issue until you then attempt to access temp of the undefined value.

Note: This can occur any time getForecast is called since it resets the responseObj state back to an empty object ({}).

Solution

You should conditionally render the props.responseObj data if the current property exists.

const conditions = (props) => {
  return (
    <div className={classes.Wrapper}>
      {props.error && (
        <small className={classes.Small}> Please enter a valid city. </small>
      )}

      {props.loading && <div className={classes.Loader} />}

      {props.responseObj.current && (
        <div className={classes.information}>
          <p className={classes.title}>
            {Math.round(props.responseObj.current.temp)}° with{" "}
            {props.responseObj.current.weather[0].description}.{" "}
          </p>
          {
            <p className={classes.info}>
              {clothes(props.unit, Math.round(props.responseObj.current.temp))}
            </p>
          }
          <p className={classes.info}>
            {rain(props.responseObj.current.weather[0].description)}
          </p>
          <p className={classes.info}>
            <img
              src={Thermo}
              alt="feels like icon"
              style={{ width: 50, height: 50 }}
            />{" "}
            Feels like:{" "}
            <b>{props.responseObj.current.feels_like.toFixed()}° </b>
          </p>
          <p className={classes.info}>
            <img
              src={Atmoshperic}
              alt="atmospheric icon"
              style={{ width: 50, marginRight: 5, height: 50 }}
            />{" "}
            Atmoshperic Pressure: <b>{props.responseObj.current.pressure}hPa</b>
          </p>
          <p className={classes.info}>
            <img
              src={Humidity}
              alt="humidity icon"
              style={{ width: 50, marginRight: 5, height: 50 }}
            />{" "}
            Humidity: <b>{props.responseObj.current.humidity}%</b>
          </p>
          <p className={classes.info}>
            <img
              src={Cloudy}
              alt="cloudy icon"
              style={{ width: 50, height: 50 }}
            />{" "}
            Cloudiness: <b>{props.responseObj.current.clouds}% </b>
          </p>
          <p className={classes.info}>
            <img
              src={Wind}
              alt="wind icon"
              style={{ width: 50, marginRight: 5, height: 50 }}
            />{" "}
            Wind:{" "}
            <b>
              {props.responseObj.current.wind_speed} m/s,{" "}
              {
                compassSector[
                  (props.responseObj.current.wind_deg / 22.5).toFixed(0)
                ]
              }{" "}
            </b>{" "}
            <img
              src={Arrow}
              alt="windarrow"
              style={{
                width: 14,
                transform: `rotate(${props.responseObj.current.wind_deg}deg)`,
                height: 14
              }}
            />
          </p>
          <p className={classes.info}>
            <img
              src={Sunrise}
              alt="sunrise icon"
              style={{ width: 50, marginRight: 5, height: 50 }}
            />{" "}
            Sunrise is at:{" "}
            <b>
              {moment.unix(props.responseObj.current.sunrise).format("HH:mm")}{" "}
            </b>
          </p>
          <p className={classes.info}>
            {" "}
            <img
              src={Sunset}
              alt="sunset icon"
              style={{ width: 50, marginRight: 5, height: 50 }}
            />
            Sunset is at:{" "}
            <b>
              {moment.unix(props.responseObj.current.sunset).format("HH:mm")}
            </b>
          </p>
        </div>
      )}
    </div>
  );
};

You could also use a ternary operator on the loading state.

{props.loading ? (
  <div className={classes.Loader} />
) : (
  <div className={classes.Wrapper}>
    .....
  </div>
)}

Or use Optional Chaining Operator on all the accesses.

const conditions = (props) => {
  return (
    <div className={classes.Wrapper}>
      {props.error && (
        <small className={classes.Small}> Please enter a valid city. </small>
      )}
      
      {props.loading && <div className={classes.Loader} />}

      <div className={classes.information}>
        <p className={classes.title}>
          {Math.round(props.responseObj.current?.temp)}° with{" "} // <-- Optional Chaining
          {props.responseObj.current?.weather[0].description}.{" "} // <-- Optional Chaining
        </p>
        {
          <p className={classes.info}>
            {clothes(props.unit, Math.round(props.responseObj.current?.temp))} // <-- Optional Chaining
          </p>
        }
        <p className={classes.info}>
          {rain(props.responseObj.current?.weather[0].description)} // <-- Optional Chaining
        </p>
        ...... etc....
      </div>
    </div>
  );
}; 

Upvotes: 2

Related Questions