Connect React to a real-world API and build a polished UI with loading states, error handling, and local history.
// hooks/useWeather.js
const API_KEY = import.meta.env.VITE_OPENWEATHER_KEY;
const BASE = 'https://api.openweathermap.org/data/2.5';
export function useWeather(city) {
const [weather, setWeather] = useState(null);
const [forecast, setForecast] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!city) return;
const controller = new AbortController();
setLoading(true);
setError(null);
Promise.all([
fetch(`${BASE}/weather?q=${city}&appid=${API_KEY}&units=metric`, { signal: controller.signal }),
fetch(`${BASE}/forecast?q=${city}&appid=${API_KEY}&units=metric`, { signal: controller.signal }),
])
.then(([r1, r2]) => Promise.all([r1.json(), r2.json()]))
.then(([w, f]) => {
if (w.cod !== 200) throw new Error(w.message);
setWeather(w);
setForecast(f.list.filter((_, i) => i % 8 === 0)); // one per day
})
.catch(err => { if (err.name !== 'AbortError') setError(err.message); })
.finally(() => setLoading(false));
return () => controller.abort();
}, [city]);
return { weather, forecast, loading, error };
}
function WeatherCard({ weather, unit }) {
const temp = unit === 'C' ? weather.main.temp : (weather.main.temp * 9/5 + 32);
return (
<div className="card">
<h2>{weather.name}, {weather.sys.country}</h2>
<img src={`https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`} alt="" />
<p className="temp">{Math.round(temp)}°{unit}</p>
<p>{weather.weather[0].description}</p>
<div className="details">
<span>💧 {weather.main.humidity}%</span>
<span>🌬 {weather.wind.speed} m/s</span>
<span>👁 {weather.visibility / 1000} km</span>
</div>
</div>
);
}
All Comments