Weather Application React JS - Add It to Your Portfolio
Weather Application React JS - Add It to Your Portfolio

In this post, we will implement a weather app in React, demonstrating through a real example how to create a React project and communicate with a real API.

finished project

To obtain weather data within our application, we need to utilize an API that provides data for a specific city. Essentially, this involves inputting a city name and retrieving data about that city from the API. To accomplish this, we will utilize the openweathermap.org service.

website

It offers a paid plan, but there is also a free tier that provides sufficient functionality for us to implement our project.

It allows us to make 1000 API requests per day for free.

Initial Project

Now let's take a look at the initial project that I created.

// src/App.jsx
import Weather from "./weather/Weather";

function App() {
  return <Weather />;
}

Inside my App component, I simply rendered the Weather component, which will contain all the logic for our application. I haven't created the Weather component yet, but I've prepared the CSS that we will use in the project.

/* src/weather/weather.css */
.weather-block {
  height: 500px;
  width: 300px;
  background: #2b3d9f;
  margin: 0 auto;
  padding: 20px;
}

.location-search {
  padding-top: 50px;
  text-align: center;
  margin-bottom: 30px;
}

.location-search-input {
  border-radius: 30px;
  padding: 8px 25px;
  outline: none;
  font-size: 18px;
}

.weather-info-image {
  border-radius: 50%;
  background: #cb2626;
}

.weather-info {
  color: white;
  text-align: center;
}

.weather-info-name {
  font-size: 34px;
  font-weight: 500;
  margin-bottom: 50px;
}

.weather-info-temp {
  font-size: 54px;
  font-weight: bold;
  margin-bottom: 10px;
}

.weather-info-details {
  margin: 0 auto;
  padding: 0 50px;
}

.weather-info-detail {
  display: flex;
  justify-content: space-between;
}

It contains all the styling needed for our project. We will be fully focused only on React; that's why I prepared the styling in advance.

Additionally, I created a data.js. The main idea is that during development, it doesn't make sense to call a real API every time we test, as it would consume our daily API calls limit.

export default {
  coord: { lon: 10, lat: 53.55 },
  weather: [
    { id: 804, main: "Clouds", description: "overcast clouds", icon: "04n" },
  ],
  base: "stations",
  main: {
    temp: 283.9,
    feels_like: 280.95,
    temp_min: 283.6,
    temp_max: 284.07,
    pressure: 1004,
    humidity: 84,
  },
  visibility: 10000,
  wind: { speed: 2.57, deg: 10 },
  clouds: { all: 100 },
  dt: 1704381631,
  sys: {
    type: 1,
    id: 1263,
    country: "DE",
    sunrise: 1704353746,
    sunset: 1704381188,
  },
  timezone: 3600,
  id: 2911298,
  name: "Hamburg",
  cod: 200,
};

Here is a mocked response from the API for Hamburg, Germany. We will use this data during the development of our application. When we are ready to fetch data from the actual API, we will make the switch. Rendering all the blocks correctly using the mocked response is much simpler.

Location Component

With that being said, lets create our Weather component.

import "./weather.css";
const LocationSearch = () => {
  return (
    <form className="location-search">
      <input
        type="text"
        placeholder="City, Country"
        className="location-search-input"
      />
    </form>
  );
};
const Weather = () => {
  return (
    <div className="weather-block">
      <LocationSearch />
    </div>
  );
};

Here, we've created our Weather component and rendered the LocationSearch component inside it. The main idea is to split our logic and implement our location search in a separate component.

basic location

The concept is that we type something into the input field, press Enter, and then we receive information about that city from the API.

const LocationSearch = ({ locationChange }) => {
  const [location, setLocation] = useState("");
  const handleSubmit = (e) => {
    e.preventDefault();
    locationChange(location);
    setLocation("");
  };
  return (
    <form className="location-search" onSubmit={handleSubmit}>
      <input
        type="text"
        value={location}
        onChange={(e) => setLocation(e.target.value)}
        placeholder="City, Country"
        className="location-search-input"
      />
    </form>
  );
};
const Weather = () => {
  const [location, setLocation] = useState(null);

  return (
    <div className="weather-block">
      <LocationSearch locationChange={(location) => setLocation(location)} />
    </div>
  );
};

Here, we've created an inner state called location for our LocationSearch component. When we submit our form, we trigger the locationChange prop that we passed into LocationSearch. Additionally, we've created a location state in our Weather component, which we change when we submit the form. Now, our Weather component knows which city's information we need to render.

Rendering Weather Data

What we want to do now is to utilize our data.js mocked response to render a weather block.

import weatherInfo from './data'

const WeatherInfo = ({ weatherInfo }) => {
  return (
    <div className="weather-info">
    </div>
  );
};

const Weather = () => {
  const [location, setLocation] = useState(null);

  return (
    <div className="weather-block">
      <LocationSearch locationChange={(location) => setLocation(location)} />
      {<WeatherInfo weatherInfo={weatherInfo} />}
    </div>
  );
};

Here, we've created a WeatherInfo component, and we pass the weatherInfo prop inside it. Currently, it doesn't make much sense as our weatherInfo is a global import, but in this case, our component is fully isolated and doesn't need to know where the data is coming from. Later, we will provide real weatherInfo to it.

Now, it's time to render all properties from weatherInfo in our weather block.

const WeatherInfo = ({ weatherInfo }) => {
  const celsiusTemperature = Math.floor(weatherInfo.main.temp - 273);
  const celsiusFeelsLikeTemperature = Math.floor(
    weatherInfo.main.feels_like - 273
  );
  return (
    <div className="weather-info">
      <img
        className="weather-info-image"
        src={`/public/${weatherInfo.weather[0].icon}.png`}
      />
      <div className="weather-info-temp">{celsiusTemperature} °C</div>
      <div className="weather-info-name">{weatherInfo.name}</div>
      <div className="weather-info-details">
        <div className="weather-info-detail">
          <b>Feels like:</b> {celsiusFeelsLikeTemperature}°C
        </div>
        <div className="weather-info-detail">
          <b>Humidity</b> {weatherInfo.main.humidity}%
        </div>

        <div className="weather-info-detail">
          <b>Wind speed:</b> {weatherInfo.wind.speed} km/h
        </div>
      </div>
    </div>
  );
};

This may seem like a lot of code, but it's mainly markup with rendering different properties from the weatherInfo object. However, there are two things worth mentioning. First, you can see the URL provided for the image. It's /public/04n.png, for example. I downloaded all possible images for the weather from this website and placed them in the /public folder.

You can download these images from the source code provided at the end of the post.

The second important point is that we receive our temperature in Fahrenheit and we need to convert it to Celsius. This is where the two variables you can see at the beginning of our function come into play.

rendered block

As you can see, we've rendered all the information on the screen, and this is exactly how our end application will look.

Fetching Weather from the API

But now comes the most important part: fetching our data from the API. To do that, you must first register on the openweathermap.org website.

api keys

You need to click on the "My API keys" link on the right side and copy a key to use it in the application.

It's important to mention that after you create an account, it may take 1-2 hours for it to be activated.

This is the url that we want to fetch.

https://api.openweathermap.org/data/2.5/weather?q=Hamburg,Germany&appid=13b8c2b59f4710181773774cff02b333

Indeed, the URL contains a query parameter for the city and country, as well as an appId, which is your API key. You cannot make a request without this key.

api call

What's important is that the entire response structure is exactly the same as our mocked data. This means that when we use it, we don't need to make any changes.

Let's add data fetching now.

import axios from 'axios'
const apiKey = "13b8c2b59f4710181773774cff02b333";
const Weather = () => {
  const [location, setLocation] = useState(null);
  const [weatherInfo, setWeatherInfo] = useState(null);

  useEffect(() => {
    if (!location) {
      return;
    }
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${location}&appid=${apiKey}`;
    axios.get(url).then((response) => {
      setWeatherInfo(response.data);
    });
  }, [location]);

  return (
    <div className="weather-block">
      <LocationSearch locationChange={(location) => setLocation(location)} />
      {weatherInfo && <WeatherInfo weatherInfo={weatherInfo} />}
    </div>
  );
};

I've created an apiKey variable and a useEffect hook. It will be triggered every time our location property changes. Inside, we make an API call using the axios library (you can install it with "npm install axios"). We provide the location and our API key to the URL and make the call. When we receive the response, we update the weatherInfo state, which is then provided to our WeatherInfo component.

I've also added a check to render our WeatherInfo component only if we have information to render.

finished project

As you can see in the browser, our project is finished, and we can now retrieve the weather for any city in the world.

And actually if you want to improve your React knowledge and prepare for the interview I highly recommend you to check my course React Interview Questions.

📚 Source code of what we've done