I am fairly new to javascript and react, but I am trying to build this weather app, with both current and future weather.
import {React, useState} from "react"
import './weather.css'
const Weather = () => {
const apiKey = 'obsured-so-that-no-one-uses-my-key'
const [weatherData, setWeatherData] = useState([{}])
const [city, setCity] = useState("")
const [forcastWeather, setForcastWeather] = useState([{}])
const getWeather = (event) => {
if (event.key === "Enter") {
{fetch(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric`)
.then(response => response.json()).then(data => {setForcastWeather(data)})}
{fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`)
.then(response => response.json()).then(data => {setWeatherData(data)})}
}
}
return (
<>
<div className="container-about-weather">
<input className="input-about-weather"
placeholder="Search"
onChange={e => setCity(e.target.value)}
value={city}
onKeyPress={getWeather}>
</input>
<div>
{typeof weatherData.main === 'undefined' ? (
<div className="weather-content-rows-beginning">
<p>Welcome to my weather app. Enter a city name
to get the weather for that city
</p>
</div>
) : (
<div className="weather-all">
<div className="weather-now">
<div className="weather-content-rows-name">
<p>{weatherData.name}, {weatherData.sys.country}</p>
</div>
<div className="weather-content-rows-temp">
<p>{Math.round(weatherData.main.temp)}°C</p>
<img src={`https://openweathermap.org/img/wn/${weatherData.weather[0].icon}@2x.png`}
alt=""/>
</div>
<div className="weather-content-rows-description">
<p>{weatherData.weather[0].description}</p>
</div>
<div className="weather-content-humidity">
<p>Humidity: {weatherData.main.humidity}</p>
</div>
<div className="weather-content-wind">
<p>Wind: {(weatherData.wind.speed * 2.23694).toFixed(1)} mph</p>
</div>
</div>
<div className="weather-forcast">
<div className="weather-forcast-days">
<img src={`https://openweathermap.org/img/wn/${forcastWeather.list[5].weather[0].icon}@2x.png`}></img>
<p className="weather-forcast-days-text">{forcastWeather.list[5].main.temp}</p>
</div>
</div>
</div>
)}
</div>
</div>
</>
)
}
export default Weather
I seem to be to make the first api call and second api call (displaying the information) when I save the page. However, when I switch page or reload, and try it again, the page goes blank until I reload it. I found an error in firefox saying "Uncaught TypeError: forcastWeather.list is undefined" and I am hoping this helps
I know this might be vague, but I was wondering if anyone could help me?
CodePudding user response:
the reason why your page is going black is the data is not there any more since you are calling them manually, in order to avoid such error you need to handle the case if those data are not available.
CodePudding user response:
Overall there are a number of improvements you could add that will help! For starters, you have two async functions -
{fetch(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric`)
.then(response => response.json()).then(data => {setForcastWeather(data)})}
{fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`)
.then(response => response.json()).then(data => {setWeatherData(data)})}
But in your render, you're basing your decision of "do I have weather data" based only on the result of the first function -
{typeof weatherData.main === 'undefined' ? (...) : (...)}
What happens if the first async function passed but the second one failed? Or what if the second async function finishes much slower than the first?
One easy way to cater for this would be to create an explicit boolean to check that both async calls have finished, before you render; something like:
const hasWeatherData = !!(weatherData.main && forcastWeather.list?.length > 0);
return (
<>
{!hasWeatherData && (...)}
{hasWeatherData && (...)}
</>
);
This is not a perfect solution! It still doesn't cater for one of the calls failing, or one of your other assumptions failing (e.g. what if forcastWeather.list
only has 3 elements in it? Then forcastWeather.list[5].main.temp
would fail).
So, another idea would be to break your component apart into smaller components wiht smaller responsibilities. For example, you could break out the "forecast" part into a smaller component:
const WeatherForecast = ({ forecastWeather }) => {
const forecast = forecastWeather?.list?.length > 5
? forecastWeather.list[5]
: undefined;
if (!forecast) {
return (
<div>Forecast weather not found</div>
);
}
return (
<div className="weather-forcast">
<div className="weather-forcast-days">
<img src={`https://openweathermap.org/img/wn/${forecast.weather[0].icon}@2x.png`}></img>
<p className="weather-forcast-days-text">{forecast.main.temp}</p>
</div>
</div>
);
}
And then call that from the parent component -
return (
<>
{/* ----- 8< ----- */}
<WeatherForecast forecastWeather={forecastWeather} />
</>
);