Are you new to React JS and looking for a practical project to boost your skills? Building a weather app is a great way to dive into React while creating something useful. It’s one of the most popular projects for beginner React developers, as it covers key concepts like working with components, managing state, and integrating APIs to fetch real-time weather data.
In this blog post, I’ll guide you through building a Weather App using React JS and CSS. This app allows users to check the weather for any city or use their current location. It provides real-time weather data along with 24-hour forecasts.
If you prefer using vanilla JavaScript, check out my blog post on Building a Weather App with HTML, CSS, and JavaScript. I’ve created the same app with all the same features. This is a great choice if you want to build your core JavaScript skills without the extra complexity of a framework.
Why Build a Weather App in React JS?
By building this weather app using React JS and CSS, you’ll gain the following skills:
- ReactJS Fundamentals: Get hands-on experience with React components, state management, and hooks.
- Location API Usage: Learn how to use the browser’s location API to fetch location-based data.
- API Integration: Understand how to interact with web services, handle asynchronous operations, and manage data fetching.
- Practical Application: Create a functional app that you can showcase in your portfolio or use in real-life scenarios.
Video Tutorial to Build Weather App in React JS
The YouTube video above is a great resource if you prefer video tutorials. It explains each line of code and provides comments, making it easy to follow along with your weather app project. If you like reading or need a step-by-step guide, keep following this post.
Setting Up the Project
Before you start building the weather app with React JS and CSS, make sure Node.js is installed on your computer. If not, download and install it from the official Node.js website.
Create a Project Folder:
- Make a new folder, for instance, “weather-app”.
- Open this folder in your VS Code editor.
Initialize the Project:
Open your terminal by pressing Ctrl + J and then use Vite to create a new React app with this command:
npm create vite@latest ./ -- --template react
Install necessary dependencies and start the development server:
npm install npm run dev
If your project is running in your browser, congratulations! You’ve successfully set up your weather app. Now, let’s move on to modifying folders and files.
Modify folder and CSS Files:
- Remove the default
assets
folder andApp.css
file. - Download the Icons folder and place it directly into your public folder, ensuring you include an icons folder with various weather icons and clouds.png images.
- Replace the content of
index.css
with the provided code.
/* Importing Google Fonts - Montserrat */ @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap'); * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Montserrat", sans-serif; } body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: linear-gradient(#F5EEFF, #DAC3F8); } #root { width: 100%; } .container { position: relative; margin: 0 auto; z-index: 1; overflow: hidden; max-width: 410px; border-radius: 10px; background: linear-gradient(#352163, #33143C); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); } .container::after { content: ""; position: absolute; left: 0; top: 0; opacity: 0.95; z-index: -1; height: 100%; width: 100%; background: url("clouds.png"); } .search-section { display: flex; gap: 10px; padding: 25px; align-items: center; } .search-section .search-form { width: 100%; height: 54px; position: relative; } .search-section .search-form span { position: absolute; color: #fff; top: 50%; left: 16px; pointer-events: none; transform: translateY(-50%); } .search-section .search-input { height: 100%; width: 100%; outline: none; color: #fff; font-size: 1rem; border-radius: 6px; text-transform: uppercase; padding: 0 20px 0 50px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.25); transition: 0.1s ease; } .search-section .search-input:focus { border-color: #a38cd9; } .search-section .search-input::placeholder { color: #ddd; text-transform: none; } .search-section .location-button { height: 54px; width: 56px; color: #fff; flex-shrink: 0; cursor: pointer; display: flex; cursor: pointer; align-items: center; justify-content: center; border-radius: 6px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.25); transition: 0.2s ease; } .search-section .location-button:hover { color: #b39fdf; border-color: #a38cd9; } .search-section .location-button span { font-size: 1.3rem; } .weather-section :where(h2, p) { color: #fff; } .current-weather { display: flex; align-items: center; padding: 20px 0 50px; flex-direction: column; } .current-weather .weather-icon { width: 140px; aspect-ratio: 1; } .current-weather .temperature { margin: 18px 0; display: flex; font-size: 3.38rem; } .current-weather .temperature span { font-size: 1.56rem; font-weight: 400; margin: 5px 0 0 2px; } .current-weather .description { font-size: 1.25rem; text-align: center; padding: 0 10px; } .hourly-forecast { padding: 16px 25px; border-top: 1px solid rgba(255, 255, 255, 0.25); } .hourly-forecast .weather-list { display: flex; gap: 40px; list-style: none; overflow-x: auto; padding-bottom: 16px; margin-bottom: -16px; scrollbar-width: thin; scrollbar-color: transparent transparent; } .hourly-forecast:hover .weather-list { scrollbar-color: #c5bcdb transparent; } .hourly-forecast .weather-item { display: flex; gap: 7px; flex-direction: column; align-items: center; } .hourly-forecast .weather-item .weather-icon { width: 28px; aspect-ratio: 1; } .no-results { min-height: 460px; display: flex; color: #fff; padding: 60px 40px 40px; text-align: center; align-items: center; flex-direction: column; } .no-results .title { margin: 25px 0 15px; } .no-results .message { line-height: 23px; } /* Responsive media query code for small screen */ @media (max-width: 624px) { body, .search-section { padding: 20px; } .hourly-forecast { padding: 16px 20px; } .hourly-forecast .weather-list { gap: 24px; } .no-results { padding: 30px; min-height: 458px; } }
Creating the Components
Within the src
directory of your project, organize your files by creating a “components” folder. Inside the components folder, create the following files:
- SearchSection.jsx
- CurrentWeather.jsx
- HourlyWeather.jsx
- NoResultsDiv.jsx
Adding the Codes
Add the respective code to each newly created file to define the layout and functionality of your weather app.
In components/SearchSection.jsx
, add the code to render the input along with the location button, implementing their respective functionalities.
const SearchSection = ({ getWeatherDetails, searchInputRef }) => { const API_KEY = import.meta.env.VITE_API_KEY; // Handles city search form submission const handleCitySearch = (e) => { e.preventDefault(); const input = e.target.querySelector(".search-input"); const API_URL = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${input.value}&days=2`; getWeatherDetails(API_URL); // Fetches weather details for the entered city }; // Gets user's current location (latitude/longitude) const handleLocationSearch = () => { navigator.geolocation.getCurrentPosition( (position) => { const { latitude, longitude } = position.coords; const API_URL = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${latitude},${longitude}&days=2`; getWeatherDetails(API_URL); // Fetches weather data for user's current location window.innerWidth >= 768 && searchInputRef.current.focus(); }, () => { alert("Location access denied. Please enable permissions to use this feature."); } ); }; return ( <div className="search-section"> <form action="#" className="search-form" onSubmit={handleCitySearch}> <span className="material-symbols-rounded">search</span> <input type="search" placeholder="Enter a city name" className="search-input" ref={searchInputRef} required /> </form> <button className="location-button" onClick={handleLocationSearch}> <span className="material-symbols-rounded">my_location</span> </button> </div> ); }; export default SearchSection;
In components/CurrentWeather.jsx
, add the code for displaying the current weather information.
const CurrentWeather = ({ currentWeather }) => { return ( <div className="current-weather"> <img src={`icons/${currentWeather.weatherIcon}.svg`} className="weather-icon" /> <h2 className="temperature"> {currentWeather.temperature} <span>°C</span> </h2> <p className="description">{currentWeather.description}</p> </div> ); }; export default CurrentWeather;
In components/HourlyWeather.jsx
, add the code for showing the hourly forecast.
import { weatherCodes } from "../constants"; const HourlyWeatherItem = ({ hourlyWeather }) => { // Extract and format temperature and time const temperature = Math.floor(hourlyWeather.temp_c); let time = hourlyWeather.time.split(" ")[1].substring(0, 5); // Find the appropriate weather icon const weatherIcon = Object.keys(weatherCodes).find((icon) => weatherCodes[icon].includes(hourlyWeather.condition.code)); return ( <li className="weather-item"> <p className="time">{time}</p> <img src={`icons/${weatherIcon}.svg`} className="weather-icon" /> <p className="temperature">{temperature}°</p> </li> ); }; export default HourlyWeatherItem;
In components/NoResultsDiv.jsx
, add the code to handle scenarios where no results are found.
const NoResultsDiv = () => { return ( <div className="no-results"> <img src="icons/no-result.svg" alt="No results found" className="icon" /> <h3 className="title">Something went wrong!</h3> <p className="message">We're unable to retrieve the weather details. Enure you've entered a valid city or try again later.</p> </div> ); }; export default NoResultsDiv;
Next, create a constants.js
file inside the src
folder to include different weather condition codes that will be used to display custom weather icons in the app.
// Maps weather condition codes to categories. // Each key represents a weather type, with an array of codes from the weather API. export const weatherCodes = { clear: [1000], clouds: [1003, 1006, 1009], mist: [1030, 1135, 1147], rain: [1063, 1150, 1153, 1168, 1171, 1180, 1183, 1198, 1201, 1240, 1243, 1246, 1273, 1276], moderate_heavy_rain: [1186, 1189, 1192, 1195, 1243, 1246], snow: [1066, 1069, 1072, 1114, 1117, 1204, 1207, 1210, 1213, 1216, 1219, 1222, 1225, 1237, 1249, 1252, 1255, 1258, 1261, 1264, 1279, 1282], thunder: [1087, 1279, 1282], thunder_rain: [1273, 1276], };
Finally, update the content of src/App.jsx
with the provided code. This file imports and utilizes the components to build the complete weather app, handling API requests and other essential functionalities that power the weather features.
import SearchSection from "./components/SearchSection"; import CurrentWeather from "./components/CurrentWeather"; import HourlyWeatherItem from "./components/HourlyWeatherItem"; import { weatherCodes } from "./constants"; import { useEffect, useRef, useState } from "react"; import NoResultsDiv from "./components/NoResultsDiv"; const App = () => { const [currentWeather, setCurrentWeather] = useState({}); const [hourlyForecasts, setHourlyForecasts] = useState([]); const [hasNoResults, setHasNoResults] = useState(false); const searchInputRef = useRef(null); const API_KEY = import.meta.env.VITE_API_KEY; const filterHourlyForecast = (hourlyData) => { const currentHour = new Date().setMinutes(0, 0, 0); const next24Hours = currentHour + 24 * 60 * 60 * 1000; // Filter the hourly data to only include the next 24 hours const next24HoursData = hourlyData.filter(({ time }) => { const forecastTime = new Date(time).getTime(); return forecastTime >= currentHour && forecastTime <= next24Hours; }); setHourlyForecasts(next24HoursData); }; // Fetches weather details based on the API URL const getWeatherDetails = async (API_URL) => { setHasNoResults(false); window.innerWidth <= 768 && searchInputRef.current.blur(); try { const response = await fetch(API_URL); if (!response.ok) throw new Error(); const data = await response.json(); // Extract current weather data const temperature = Math.floor(data.current.temp_c); const description = data.current.condition.text; const weatherIcon = Object.keys(weatherCodes).find((icon) => weatherCodes[icon].includes(data.current.condition.code)); setCurrentWeather({ temperature, description, weatherIcon }); // Combine hourly data from both forecast days const combinedHourlyData = [...data.forecast.forecastday[0].hour, ...data.forecast.forecastday[1].hour]; searchInputRef.current.value = data.location.name; filterHourlyForecast(combinedHourlyData); } catch { // Set setHasNoResults state if there's an error setHasNoResults(true); } }; // Fetch default city (London) weather data on initial render useEffect(() => { const defaultCity = "London"; const API_URL = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${defaultCity}&days=2`; getWeatherDetails(API_URL); }, []); return ( <div className="container"> {/* Search section */} <SearchSection getWeatherDetails={getWeatherDetails} searchInputRef={searchInputRef} /> {/* Conditionally render based on hasNoResults state */} {hasNoResults ? ( <NoResultsDiv /> ) : ( <div className="weather-section"> {/* Current weather */} <CurrentWeather currentWeather={currentWeather} /> {/* Hourly weather forecast list */} <div className="hourly-forecast"> <ul className="weather-list"> {hourlyForecasts.map((hourlyWeather) => ( <HourlyWeatherItem key={hourlyWeather.time_epoch} hourlyWeather={hourlyWeather} /> ))} </ul> </div> </div> )} </div> ); }; export default App;
Important: Your weather app won’t function until you connect it with a weather API. Sign up for a free API key from WeatherAPI. Once you have your key, create a .env
file in the root directory of your project and add the following code with your API key.
VITE_API_KEY=YOUR-API-KEY-HERE
Conclusion and final words
Congratulations! If you’ve followed all the steps correctly, your Weather App should now be visible in your browser. You can test it by entering a city name or using the location feature to fetch weather data for any city.
Building this app not only enhances your React skills but also gives you a practical tool that you can use and share. Don’t stop here—keep experimenting! Consider adding features like a 7-day forecast, weather alerts, or even integrating a dark mode for a more user-friendly experience.
If you encounter any issues, feel free to download the source code files for this weather project for free by clicking the “Download” button. Be sure to read the README.md
file included in the zip for detailed instructions on how to set up and run the project.