ReReact · Lesson 7 of 8

Mini Project: Weather Dashboard

Let's build a weather dashboard that uses everything we've learned: components, props, state, effects, and a custom hook for data fetching.

JSX
// WeatherDashboard.jsx
import { useState, useEffect } from 'react'

function useFetch(url) {
  const [state, setState] = useState({ data: null, loading: !!url, error: null })

  useEffect(() => {
    if (!url) return
    let cancelled = false
    setState({ data: null, loading: true, error: null })

    fetch(url)
      .then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
      .then(data => !cancelled && setState({ data, loading: false, error: null }))
      .catch(err => !cancelled && setState({ data: null, loading: false, error: err }))

    return () => { cancelled = true }
  }, [url])

  return state
}

function WeatherCard({ data }) {
  const wmo = { 0: "☀️ Clear", 1: "🌤 Mostly Clear", 2: "⛅ Partly Cloudy",
                3: "☁️ Overcast", 51: "🌦 Drizzle", 61: "🌧 Rain", 80: "🌧 Showers" }

  const icon = wmo[data.current.weather_code] ?? "🌡"

  return (
    <div className="weather-card">
      <h2>{icon}</h2>
      <p className="temp">{data.current.temperature_2m}°C</p>
      <p>Wind: {data.current.wind_speed_10m} km/h</p>
      <p>Humidity: {data.current.relative_humidity_2m}%</p>
    </div>
  )
}

const CITIES = [
  { name: "London",   lat: 51.5074, lon: -0.1278 },
  { name: "New York", lat: 40.7128, lon: -74.0060 },
  { name: "Tokyo",    lat: 35.6762, lon: 139.6503 },
]

function WeatherDashboard() {
  const [city, setCity] = useState(CITIES[0])

  const url = `https://api.open-meteo.com/v1/forecast`
    + `?latitude=${city.lat}&longitude=${city.lon}`
    + `&current=temperature_2m,wind_speed_10m,weather_code,relative_humidity_2m`

  const { data, loading, error } = useFetch(url)

  return (
    <div>
      <h1>Weather Dashboard</h1>
      <div>
        {CITIES.map(c => (
          <button key={c.name} onClick={() => setCity(c)}
                  style={{ fontWeight: city.name === c.name ? "bold" : "normal" }}>
            {c.name}
          </button>
        ))}
      </div>
      <h2>{city.name}</h2>
      {loading && <p>Loading...</p>}
      {error   && <p>Error: {error.message}</p>}
      {data    && <WeatherCard data={data} />}
    </div>
  )
}

export default WeatherDashboard
✦ Tip
Add this to `main.jsx` and it works with zero backend. Open-Meteo is free and requires no API key. Try extending it: add a 7-day forecast, a dark/light theme toggle with useContext, or LocalStorage to persist the last selected city.