Reputation: 153
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
Reputation: 202864
It looks like you are accessing into an undefined property on the initial render.
Forecast
the initial responseObj
state is an empty object and loading
state is initially false.{}
responseObj
value is passed to Conditions
.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 ({}
).
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