Skip to content

Commit 58acabb

Browse files
Merge pull request #24 from aditya241104/feat/weather-api-service
feat: Extract Weather API calls to service with caching #23
2 parents 4242a93 + 32e8877 commit 58acabb

File tree

2 files changed

+141
-48
lines changed

2 files changed

+141
-48
lines changed

src/pages/Weather.jsx

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,29 @@
22
* WEATHER DASHBOARD TODOs
33
* -----------------------
44
* Easy:
5-
* - [ ] Add °C / °F toggle
6-
* - [ ] Show weather icon (current + forecast)
7-
* - [ ] Show feels-like temperature & wind speed
8-
* - [ ] Add loading skeleton instead of plain text
9-
* - [ ] Style forecast cards with condition color badges
5+
* - [x] Extract API call into /src/services/weather.js and add caching
6+
* - [ ] Add °C / °F toggle
7+
* - [ ] Show weather icon (current + forecast)
8+
* - [ ] Show feels-like temperature & wind speed
9+
* - [ ] Add loading skeleton instead of plain text
10+
* - [ ] Style forecast cards with condition color badges
1011
* Medium:
11-
* - [ ] Dynamic background / gradient based on condition (sunny, rain, snow)
12-
* - [ ] Input debounced search (on stop typing)
13-
* - [ ] Persist last searched city (localStorage)
14-
* - [ ] Add error retry button component
15-
* - [ ] Add favorites list (pin cities)
12+
* - [ ] Dynamic background / gradient based on condition (sunny, rain, snow)
13+
* - [ ] Input debounced search (on stop typing)
14+
* - [ ] Persist last searched city (localStorage)
15+
* - [ ] Add error retry button component
16+
* - [ ] Add favorites list (pin cities)
1617
* Advanced:
17-
* - [ ] Hourly forecast visualization (line / area chart)
18-
* - [ ] Animate background transitions
19-
* - [ ] Add geolocation: auto-detect user city (with permission)
20-
* - [ ] Extract API call into /src/services/weather.js and add caching
18+
* - [ ] Hourly forecast visualization (line / area chart)
19+
* - [ ] Animate background transitions
20+
* - [ ] Add geolocation: auto-detect user city (with permission)
2121
*/
22+
2223
import { useEffect, useState } from 'react';
2324
import Loading from '../components/Loading.jsx';
2425
import ErrorMessage from '../components/ErrorMessage.jsx';
2526
import Card from '../components/Card.jsx';
27+
import { getWeatherData, clearWeatherCache, getCacheStats } from '../services/weather.js';
2628

2729
export default function Weather() {
2830
const [city, setCity] = useState('London');
@@ -33,15 +35,14 @@ export default function Weather() {
3335

3436
useEffect(() => {
3537
fetchWeather(city);
36-
// eslint-disable-next-line react-hooks/exhaustive-deps
38+
// eslint-disable-next-line react-hooks/exhaustive-deps
3739
}, []);
3840

3941
async function fetchWeather(c) {
4042
try {
41-
setLoading(true); setError(null);
42-
const res = await fetch(`https://wttr.in/${encodeURIComponent(c)}?format=j1`);
43-
if (!res.ok) throw new Error('Failed to fetch');
44-
const json = await res.json();
43+
setLoading(true);
44+
setError(null);
45+
const json = await getWeatherData(c); // Using the service instead of direct fetch
4546
setData(json);
4647
} catch (e) {
4748
setError(e);
@@ -50,47 +51,76 @@ export default function Weather() {
5051
}
5152
}
5253

54+
// Helper function to clear cache and refetch (for testing)
55+
const handleClearCache = () => {
56+
clearWeatherCache();
57+
fetchWeather(city);
58+
};
59+
60+
// Helper function to show cache stats (for development)
61+
const handleShowCacheStats = () => {
62+
const stats = getCacheStats();
63+
console.log('Cache Statistics:', stats);
64+
alert(`Cache has ${stats.size} entries. Check console for details.`);
65+
};
66+
5367
const current = data?.current_condition?.[0];
5468
const forecast = data?.weather?.slice(0,3) || [];
5569

5670
// Helper to convert °C to °F
5771
const displayTemp = (c) => unit === 'C' ? c : Math.round((c * 9/5) + 32);
5872

5973
return (
60-
<div>
61-
<h2>Weather Dashboard</h2>
62-
<form onSubmit={e => { e.preventDefault(); fetchWeather(city); }} className="inline-form">
63-
<input value={city} onChange={e => setCity(e.target.value)} placeholder="Enter city" />
64-
<button type="submit">Fetch</button>
65-
</form>
66-
67-
{/* Toggle button */}
68-
<div style={{ margin: '10px 0' }}>
69-
<button onClick={() => setUnit(unit === 'C' ? 'F' : 'C')}>
70-
Switch to °{unit === 'C' ? 'F' : 'C'}
71-
</button>
74+
<div className="dashboard-page">
75+
<div className="dashboard-header">
76+
<h1>🌤️ Weather Dashboard</h1>
77+
<form onSubmit={(e) => {e.preventDefault(); fetchWeather(city)}}>
78+
<input
79+
type="text"
80+
value={city}
81+
onChange={(e) => setCity(e.target.value)}
82+
placeholder="Enter city name..."
83+
/>
84+
<button type="submit">Get Weather</button>
85+
</form>
86+
87+
{/* Development tools - you can remove these later */}
88+
<div style={{marginTop: '10px', display: 'flex', gap: '10px'}}>
89+
<button onClick={handleClearCache} style={{fontSize: '12px'}}>
90+
Clear Cache
91+
</button>
92+
<button onClick={handleShowCacheStats} style={{fontSize: '12px'}}>
93+
Cache Stats
94+
</button>
95+
<button onClick={() => setUnit(unit === 'C' ? 'F' : 'C')} style={{fontSize: '12px'}}>
96+
Switch to °{unit === 'C' ? 'F' : 'C'}
97+
</button>
98+
</div>
7299
</div>
73100

74101
{loading && <Loading />}
75-
<ErrorMessage error={error} />
102+
{error && <ErrorMessage message={error.message} onRetry={() => fetchWeather(city)} />}
76103

77-
{current && (
78-
<Card title={`Current in ${city}`}>
79-
<p>Temperature: {displayTemp(Number(current.temp_C))}°{unit}</p>
80-
<p>Humidity: {current.humidity}%</p>
81-
<p>Desc: {current.weatherDesc?.[0]?.value}</p>
82-
</Card>
83-
)}
84-
85-
<div className="grid">
86-
{forecast.map(day => (
87-
<Card key={day.date} title={day.date}>
88-
<p>Avg Temp: {displayTemp(Number(day.avgtempC))}°{unit}</p>
89-
<p>Sunrise: {day.astronomy?.[0]?.sunrise}</p>
104+
{data && !loading && (
105+
<div className="dashboard-grid">
106+
{/* Current Weather */}
107+
<Card title="Current Weather" size="large">
108+
<h2>{data.nearest_area?.[0]?.areaName?.[0]?.value || city}</h2>
109+
<p><strong>Temperature:</strong> {displayTemp(Number(current.temp_C))}°{unit}</p>
110+
<p><strong>Humidity:</strong> {current.humidity}%</p>
111+
<p><strong>Desc:</strong> {current.weatherDesc?.[0]?.value}</p>
90112
</Card>
91-
))}
92-
</div>
113+
114+
{/* 3-Day Forecast */}
115+
{forecast.map((day, i) => (
116+
<Card key={i} title={i === 0 ? 'Today' : `Day ${i+1}`}>
117+
<p><strong>Avg Temp:</strong> {displayTemp(Number(day.avgtempC))}°{unit}</p>
118+
<p><strong>Sunrise:</strong> {day.astronomy?.[0]?.sunrise}</p>
119+
<p><strong>Sunset:</strong> {day.astronomy?.[0]?.sunset}</p>
120+
</Card>
121+
))}
122+
</div>
123+
)}
93124
</div>
94125
);
95126
}
96-

src/services/weather.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// src/services/weather.js
2+
3+
// Simple in-memory cache
4+
const weatherCache = new Map();
5+
const CACHE_DURATION = 10 * 60 * 1000; // 10 minutes in milliseconds
6+
7+
/**
8+
* Fetches weather data for a given city with caching
9+
* @param {string} city - The city name to fetch weather for
10+
* @returns {Promise<Object>} Weather data from wttr.in API
11+
*/
12+
export const getWeatherData = async (city) => {
13+
const cacheKey = city.toLowerCase();
14+
const now = Date.now();
15+
16+
// Check if we have cached data that's still valid
17+
if (weatherCache.has(cacheKey)) {
18+
const cachedData = weatherCache.get(cacheKey);
19+
if (now - cachedData.timestamp < CACHE_DURATION) {
20+
return cachedData.data;
21+
} else {
22+
// Remove expired cache entry
23+
weatherCache.delete(cacheKey);
24+
}
25+
}
26+
27+
try {
28+
const res = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`);
29+
30+
if (!res.ok) {
31+
throw new Error('Failed to fetch');
32+
}
33+
34+
const json = await res.json();
35+
36+
// Cache the successful response
37+
weatherCache.set(cacheKey, {
38+
data: json,
39+
timestamp: now
40+
});
41+
42+
return json;
43+
} catch (error) {
44+
throw error; // Re-throw to maintain existing error handling
45+
}
46+
};
47+
48+
/**
49+
* Clears the weather cache
50+
*/
51+
export const clearWeatherCache = () => {
52+
weatherCache.clear();
53+
};
54+
55+
/**
56+
* Gets cache statistics for debugging
57+
*/
58+
export const getCacheStats = () => {
59+
return {
60+
size: weatherCache.size,
61+
entries: Array.from(weatherCache.keys())
62+
};
63+
};

0 commit comments

Comments
 (0)