⛅ Build A Weather App in React JS & CSS | Step-By-Step Guide

0

Build A Weather App in React JS & CSS

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 and App.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&apos;re unable to retrieve the weather details. Enure you&apos;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.

 

Previous articleTop App Development Trends to Watch in 2025

LEAVE A REPLY

Please enter your comment!
Please enter your name here