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.
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.
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.
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.
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.
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.
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.
As you can see in the browser, our project is finished, and we can now retrieve the weather for any city in the world.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done