Skip to content

Ashutoshraj244/skycast

Repository files navigation

SkyCast — Real-Time Weather Dashboard

A responsive weather dashboard built with React and the OpenWeatherMap API. Search any city to get current conditions, a 5-day forecast, and 48-hour trend charts.

SkyCast screenshot placeholder


Overview

SkyCast started as a weekend project to practice API integration and state management in React. The goal wasn't to build another flashy weather app — it was to build something I'd actually use: practical, readable, and reasonably fast.

The feature set covers:

  • Real-time current weather (temperature, humidity, wind, pressure, visibility, sunrise/sunset)
  • 5-day daily forecast with high/low ranges and precipitation probability
  • Hourly horizontal-scroll view for the next 24 hours
  • 48-hour temperature area chart and humidity bar chart (Recharts)
  • Celsius/Fahrenheit toggle with localStorage persistence
  • Debounced search with recent city history (up to 6 entries)
  • Full loading/error/empty state handling

Motivation

I'd used weather APIs in class exercises but always in a simplified "fetch and display" pattern. I wanted to work through the messier parts: what happens when the network is slow? What if the user searches while a previous request is still in-flight? How do you handle a 429 rate-limit error differently from a 404?

This project was an opportunity to do that with a real API and a real UI that someone might actually interact with.


Tech Decisions

React (Vite) — Vite's HMR is noticeably faster than CRA for iterative UI work. React's component model maps well to a dashboard with several independently-loading sections.

Tailwind CSS — I went back and forth on this. Tailwind makes responsive layout fast, but it can produce visually generic output if you're not deliberate about it. I defined a custom design token layer (neutral background colors, IBM Plex typography, one accent blue) to avoid the default Tailwind look.

Context API + useReducer — Zustand would've been cleaner in some ways, but useReducer + context is enough for this scope. The weather state is essentially one data tree — current conditions, forecast, unit preference, recent searches — and doesn't need the complexity of a full state library. Adding Zustand later would be a 30-minute refactor.

Axios — Fetch works fine, but Axios gives me timeout support and a clean interceptor pattern if I ever want to add request caching or retry logic. The timeout is set to 10 seconds to handle genuinely slow API responses without hanging indefinitely.

Recharts — Recharts is lightweight and composable. I only needed two chart types (area + bar) and didn't want to pull in a heavier library.

date-fns — Moment.js is overkill and adds ~200kB. date-fns is tree-shakeable and covers everything I needed: day formatting, Unix timestamp conversion, timezone offset math.


Architecture

src/
├── api/            # Axios calls, error normalization
├── components/
│   ├── charts/     # TempChart, HumidityChart (Recharts)
│   ├── common/     # Skeletons, ErrorBanner, EmptyState, UnitToggle
│   ├── forecast/   # ForecastPanel, HourlyScroll
│   ├── search/     # SearchBar with dropdown
│   └── weather/    # CurrentWeather panel
├── context/        # WeatherContext (useReducer)
├── hooks/          # useWeatherSearch, useDebounce, useLocalStorage
├── layouts/        # AppLayout (header + main wrapper)
├── pages/          # Dashboard
└── utils/          # Formatting helpers (temp, wind, time, grouping)

API layer (src/api/weather.js): All API calls go through here. Errors are normalized into user-facing strings at this boundary, so the rest of the app never has to inspect Axios error shapes. This also means if I switch to a different HTTP client later, the change is isolated.

State (src/context/WeatherContext.jsx): A single useReducer handles loading, current weather, forecast, error, unit preference, and recent cities. The reducer is the only place state transitions happen — components only dispatch actions or call the helper callbacks (setUnit, addRecent, clearError).

Hooks: useWeatherSearch handles the dual fetch (current + forecast) and uses a token-based abort pattern to ignore stale responses if the user searches again quickly. useDebounce gates the recent-search suggestions filter.

Utils: All data formatting lives in src/utils/weather.js. The forecast grouping logic (splitting OWM's 3-hour list into days, picking midday snapshots, calculating day ranges) lives here rather than in components.


Challenges

Race conditions on search — OpenWeatherMap's free tier is fast enough that this rarely shows visually, but it's still a correctness issue. If a user types "London", clears it, and types "Paris" before the first request completes, both responses would try to update the UI. I used a simple token object (a mutable ref compared at dispatch time) to discard stale responses.

Unit switching — The OWM API returns temperature in the unit you request. So switching between Celsius and Fahrenheit means re-fetching with the new unit parameter, not doing client-side math. I trigger a re-search when the unit changes and there's an existing city loaded.

Forecast grouping — OWM's /forecast endpoint returns data in 3-hour intervals over 5 days (up to 40 items). Grouping these into calendar days, picking a representative icon and description, and calculating min/max temps took more parsing logic than I expected. The groupForecastByDay util handles this.

Timezone display — OWM returns a timezone offset in seconds from UTC. Displaying local sunrise/sunset times (not UTC, not local-browser time) required converting Unix timestamps using that offset. date-fns doesn't have a lightweight timezone-aware formatter, so I handled this manually with raw UTC math.

Error messaging — I wanted to distinguish between "city not found" (404), "bad API key" (401), "rate limited" (429), and network failures, and show appropriate messages for each. The normalization in api/weather.js handles this cleanly.


Local Setup

Prerequisites: Node 18+

git clone https://github.com/yourusername/skycast.git
cd skycast
npm install

API key setup:

  1. Register at openweathermap.org (free tier is enough)
  2. Copy .env.example to .env:
    cp .env.example .env
  3. Paste your API key:
    VITE_OWM_API_KEY=your_key_here
    

Run locally:

npm run dev

Opens at http://localhost:5173.

Build:

npm run build
npm run preview   # preview the production build locally

Future Improvements

  • Geolocation — "Use my location" button using the browser's Geolocation API. Already have fetchByCoords in the API layer.
  • PWA + offline — Service worker to cache the last loaded city. Useful on flaky mobile connections.
  • Severe weather alerts — OWM's /onecall endpoint includes alert data. Would need a subscription tier.
  • Weather map layer — OWM provides tile layers for precipitation and cloud cover that can be layered on a Leaflet map.
  • Unit auto-detection — Detect locale and default to the appropriate unit rather than always starting in Celsius.
  • Theme toggle — The color system is set up with CSS custom properties; dark mode would be a reasonable addition.
  • Request caching — Cache API responses in sessionStorage with a 10-minute TTL to reduce repeat requests for the same city.

Reflection

The most useful thing I practiced here wasn't any specific feature — it was thinking about the user's experience when things go wrong. The happy path (search works, data loads, charts render) is easy. What took more thought was: what does the user see while waiting? What do they see if they searched a typo? What if they're on a slow connection and search twice?

Getting the error handling right, and making loading states feel fast rather than jittery, took more iteration than I expected. The skeleton loaders in particular went through several revisions — too flashy initially, then too understated, eventually landing somewhere that felt like a real app.

If I were doing this again, I'd probably start with Zustand rather than Context + useReducer. The Context approach works, but adding the unit-toggle re-fetch behavior required some awkward coupling between the context state and the search hook. Zustand's subscribe pattern would have handled that more cleanly.

I'd also look at React Query for the data fetching layer. The manual abort-token pattern I implemented is essentially a subset of what React Query's staleTime and query cancellation handle automatically.


About

A responsive weather dashboard built with React and the OpenWeatherMap API. Search any city to get current conditions, a 5-day forecast, and 48-hour trend charts.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors