Skip to Content
Module 5: Side Effects & Hooks🎯 Mini Project: Weather Dashboard

Module 5.4: Mini Project 4 - Weather Dashboard

Banner image of a weather station floating in the clouds, with holographic panels showing weather data.

(AI Image Gen): > Prompt: A stunning 3D isometric weather station floating in the cloud. The station features holographic panels displaying “Temperature”, “Humidity”, and “Wind Speed”. One panel shows a bright sun icon, another shows rain. The base connects to a “Server” block via glowing data cables. Weather elements like small clouds and lightning bolts float nearby. High-tech, clean, vibrant orange and blue color palette.

🛠️ Image Assets Script

Run the following script in your terminal to generate placeholder image files:

mkdir -p images/b05-4 touch images/b05-4/b05-4-banner.png touch images/b05-4/b05-4-app-flow.png touch images/b05-4/b05-4-final-result.png

🎯 Learning Objectives

In this project, we’ll solidify our understanding of useEffect, Custom Hooks, and Data Fetching as we build a weather forecasting application.

Key Features:

  • Real-time City Search: Implement city search with Debouncing for a smooth user experience.
  • API Integration: Connect to the OpenWeatherMap API to fetch live weather data.
  • Auto Refresh: Automatically update weather data every 30 seconds.
  • Error Handling: Gracefully handle scenarios where a city isn’t found or network issues occur.

1. Getting Your API Key

To fetch weather data, you’ll need to sign up for a free account on OpenWeatherMap.

  1. Head over to OpenWeatherMap and create your account.
  2. Navigate to the My API Keys section.
  3. Copy your API Key (it looks something like e4b5...).

Note: Newly generated API Keys can take 10-15 minutes to become active.

2. Project Structure

We’ll leverage the Custom Hooks you learned in Module 5.3.

src/ ├── hooks/ │ ├── useDebounce.ts (Created in lesson 5.3) │ └── useWeather.ts (New - Handles API call logic) ├── components/ │ ├── SearchBar.tsx (Search UI) │ └── WeatherCard.tsx (Data display UI) └── App.tsx

App Flowchart illustrating the data flow within the weather application.

(AI Image Gen): > Prompt: A technical flowchart showing the data flow of the Weather App.

  1. User types in “Search Input”.
  2. “useDebounce” waits 500ms.
  3. “useWeather” Hook triggers API Request.
  4. API returns JSON.
  5. “WeatherCard” displays data.
  6. Parallel loop: “setInterval” triggers “useWeather” every 30s. Minimalist, schematic style.

3. Building the useWeather Hook

Let’s separate the API calling and auto-refresh logic from the UI.

File: src/hooks/useWeather.ts

import { useState, useEffect } from 'react'; const API_KEY = 'YOUR_API_KEY_HERE'; // Replace with your key const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather'; // Define the interface for the API response data interface WeatherData { name: string; main: { temp: number; humidity: number; }; weather: { description: string; icon: string; }[]; wind: { speed: number; }; } export default function useWeather(city: string) { const [data, setData] = useState<WeatherData | null>(null); const [loading, setLoading] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); const fetchWeather = async () => { // No need to fetch if there's no city specified if (!city) return; setLoading(true); setError(null); try { const response = await fetch(`${BASE_URL}?q=${city}&appid=${API_KEY}&units=metric&lang=vi`); if (!response.ok) { // Throw an error if the city is not found or another HTTP error occurs throw new Error('City not found'); } const result: WeatherData = await response.json(); setData(result); } catch (err: any) { // Set the error message, defaulting to a generic one if none provided setError(err.message || 'An error occurred'); setData(null); // Clear previous data on error } finally { setLoading(false); } }; // Effect 1: Fetch weather data whenever the city changes useEffect(() => { fetchWeather(); }, [city]); // Dependency array: re-run effect if 'city' changes // Effect 2: Set up an interval for auto-refreshing data every 30 seconds useEffect(() => { // Don't set up the interval if no city is selected if (!city) return; const intervalId = setInterval(() => { console.log('🔄 Auto-refreshing weather data...'); fetchWeather(); }, 30000); // 30 seconds interval // Cleanup function: clear the interval when the component unmounts or 'city' changes return () => clearInterval(intervalId); }, [city]); // Dependency array: re-run effect if 'city' changes return { data, loading, error }; }

4. Building the UI Components

4.1. SearchBar Component

File: src/components/SearchBar.tsx

interface SearchBarProps { value: string; onChange: (value: string) => void; } export default function SearchBar({ value, onChange }: SearchBarProps) { return ( <div className="mb-6"> <input type="text" placeholder="Enter city name (e.g., Hanoi)..." className="w-full p-4 rounded-xl border-2 border-blue-200 focus:border-blue-500 focus:outline-none shadow-sm text-lg" value={value} onChange={(e) => onChange(e.target.value)} /> </div> ); }

4.2. WeatherCard Component

File: src/components/WeatherCard.tsx

// Reuse the interface defined in useWeather.ts or import from shared types interface WeatherData { name: string; main: { temp: number; humidity: number; }; weather: { description: string; icon: string; }[]; wind: { speed: number; }; } interface WeatherCardProps { data: WeatherData | null; } export default function WeatherCard({ data }: WeatherCardProps) { // Render nothing if no data is available if (!data) return null; const { name, main, weather, wind } = data; // Construct the URL for the weather icon const iconUrl = `https://openweathermap.org/img/wn/${weather[0].icon}@2x.png`; return ( <div className="bg-white p-8 rounded-3xl shadow-xl text-center animate-fade-in"> <h2 className="text-3xl font-bold text-gray-800 mb-2">{name}</h2> <p className="text-gray-500 capitalize">{weather[0].description}</p> <div className="flex justify-center items-center my-6"> <img src={iconUrl} alt="Weather Icon" className="w-24 h-24" /> <span className="text-6xl font-extrabold text-blue-600">{Math.round(main.temp)}°</span> </div> <div className="grid grid-cols-2 gap-4 mt-6"> <div className="bg-blue-50 p-4 rounded-xl"> <p className="text-sm text-gray-500">Humidity</p> <p className="text-xl font-bold text-blue-800">{main.humidity}%</p> </div> <div className="bg-blue-50 p-4 rounded-xl"> <p className="text-sm text-gray-500">Wind</p> <p className="text-xl font-bold text-blue-800">{wind.speed} m/s</p> </div> </div> </div> ); }

5. Integration (App.tsx)

We’ll use useDebounce to prevent excessive API calls while the user is typing.

File: src/App.tsx

import { useState } from 'react'; import useWeather from './hooks/useWeather'; import useDebounce from './hooks/useDebounce'; // Reuse from Module 5.3 import SearchBar from './components/SearchBar'; import WeatherCard from './components/WeatherCard'; export default function App() { // Initialize with a default city const [searchTerm, setSearchTerm] = useState < string > 'Ho Chi Minh'; // The debouncedSearch variable will only update after the user stops typing for 500ms const debouncedSearch = useDebounce(searchTerm, 500); // The useWeather hook will react to changes in debouncedSearch const { data, loading, error } = useWeather(debouncedSearch); return ( <div className="min-h-screen bg-gradient-to-br from-cyan-500 to-blue-600 flex items-center justify-center p-4"> <div className="w-full max-w-md"> <h1 className="text-4xl font-bold text-white text-center mb-8">Weather App 🌤️</h1> <SearchBar value={searchTerm} onChange={setSearchTerm} /> {/* Display loading state */} {loading && <div className="text-white text-center text-xl animate-pulse">Loading data...</div>} {/* Display error state */} {error && <div className="bg-red-100 text-red-600 p-4 rounded-xl text-center mb-4">⚠️ {error}</div>} {/* Display fetched weather data */} {!loading && !error && <WeatherCard data={data} />} </div> </div> ); }

A realistic smartphone mockup displaying the completed Weather App interface.

(AI Image Gen): > Prompt: A realistic smartphone mockup displaying the specific Weather App interface we built. The screen shows “Ho Chi Minh City”, “32°C”, a large sun icon, and humidity/wind grids. The background of the phone screen is a soft blue gradient. The phone is floating against a blurred minimalist living room background.

6. Recap

Congratulations on completing Mini Project 4! You’ve now mastered:

  1. Data Fetching: Using fetch and async/await to retrieve real-world data.
  2. Custom Hooks: Encapsulating complex API logic (like in useWeather) to keep your UI clean.
  3. Debounce Strategy: Optimizing search performance with useDebounce.
  4. Auto Refresh: Implementing background data updates using setInterval in conjunction with useEffect.

7. Advanced Exercises (Optional)

  1. 5-Day Forecast: Integrate the Forecast API to display a 5-day weather outlook.
  2. Persistence: Save the last searched city to localStorage so it persists across page reloads.
  3. Dynamic Backgrounds: Automatically change the app’s background (sunny, rainy, night) based on the data returned from the API.
Last updated on