A weather finder built with React 19 and TypeScript. You type a city and pick a country, and the app shows the current temperature with the day's minimum and maximum, using data from the OpenWeatherMap API.
This is Project 08 of the Udemy course React de Principiante a Experto. The goal of this project is to consume an external API with Axios, validate its response at runtime with Valibot, keep the fetching logic in a custom hook, and style components with CSS Modules.
- Search the current weather by city and country.
- Both fields are required. An alert shows up if one is empty.
- The temperature is shown in Celsius, with the minimum and maximum.
- A spinner is shown while the request is in progress.
- A "Ciudad No Encontrada" message is shown when the city does not exist.
- Two-column layout on wide screens, one column on mobile.
- React 19
- TypeScript
- Vite
- CSS Modules for component-scoped styles
- Axios for HTTP requests
- Valibot to validate the API response
- OpenWeatherMap API (Geocoding and Current Weather)
- ESLint with
typescript-eslint
All the fetching logic lives in the useWeather custom hook. App only reads its state and passes fetchWeather down to the form.
Form ──fetchWeather({ city, country })──▶ useWeather
│
├─ 1. Geocoding API → city, country ▶ lat, lon
│ (no result → notFound = true)
├─ 2. Weather API → lat, lon ▶ raw JSON
└─ 3. Valibot parse(WeatherSchema) ▶ typed Weather
│
App ◀── weather, loading, notFound, hasWeatherData ─────────────────────────────────────┘
├── loading → <Spinner />
├── hasWeatherData → <WeatherDetail />
└── notFound → <Alert>
The API returns temperatures in Kelvin. The formatTemperature helper converts them to Celsius before they are shown.
src/
├── components/
│ ├── Alert/ # Message for validation errors and "city not found"
│ ├── Form/ # City input and country select
│ ├── spinner/ # Loading spinner (plain CSS)
│ └── WeatherDetail/ # Card with the current, min and max temperature
├── data/
│ └── countries.ts # Countries available in the select
├── helpers/
│ └── index.ts # Kelvin to Celsius conversion
├── hooks/
│ └── useWeather.ts # API calls, Valibot schema and weather state
├── types/
│ └── index.ts # SearchType and Country types
├── App.tsx # Layout and conditional rendering
├── index.css # Global styles and CSS variables
└── main.tsx
Each component keeps its styles next to it in a *.module.css file.
- Consuming an external API with Axios. Two chained requests: the Geocoding API turns a city name into coordinates, and the Weather API uses them to get the current weather.
- Environment variables in Vite. The API key is read from
import.meta.env.VITE_API_KEY, and only variables with theVITE_prefix reach the client code. - Validating API responses at runtime. TypeScript types disappear at runtime, so an external response cannot be trusted just because it is typed. A Valibot schema checks the shape of the response, and
InferOutputderives theWeathertype from that same schema. - Custom hooks.
useWeatherholds the state and the fetching logic, so the components only render what it returns. - Handling loading and error states.
loadingshows the spinner,notFoundshows the alert, andfinallymakes sure the spinner is hidden whatever happens. - CSS Modules. Class names are scoped to each component, so two components can use
.containerwithout clashing. - Typing events and props.
ChangeEventfor inputs and selects,SubmitEventfor the form, andReactNodefor components that receivechildren.
Requirements: Node.js 20 or later and a free OpenWeatherMap API key.
# Clone the repository
git clone https://github.com/TarekM-7/clima-react-typescript.git
cd clima-react-typescript
# Install dependencies
npm install
# Add your API key
cp .env.example .env
# then set VITE_API_KEY in .env
# Start the dev server
npm run devOther scripts:
npm run build # Type-check and build for production
npm run preview # Preview the production build
npm run lint # Run ESLintNote: every
VITE_variable ends up in the browser bundle, so the API key is visible to anyone using the deployed app. That is acceptable for a free key in a learning project. A real app would call the API from a backend.
Project idea and design come from the Udemy course linked above. I wrote the implementation while following the course and adapted it as I learned.
