In the previous lesson, we built a todo list application using React. This project gave a solid overview of key concepts in React such as JSX, components, state management, props, and event handlers, provided hands-on experience with how these concepts work together to create dynamic, interactive user interfaces.
Now that we've covered the basics, let's take one step further and create a weather app. Through this project, we will explore how to create a more dynamic application by working with APIs in React. Here is a demonstration:
Open demo in new tabGetting OpenWeatherMap API
To begin with, make sure to get a free API key from OpenWeatherMap. This is a website that provides global weather data via API, including current weather data, forecasts, and historical weather data.
Create an account and navigate to https://home.openweathermap.org/api_keys. Provide a name for the API key, and click Generate. A new API key should be generated.
Remember to save the API key, as we are going to use it later.
Go through the documentation for the OpenWeatherMap API, and notice that it requires us to provide the longitude and latitude in order to retrieve the weather. However, thinking from the user's perspective, it is unlikely for them to know the coordinate of the location they are searching for.
As a result, out weather app should allow the user to provide the name of the city, and in the background, the app needs to find out the coordinate of that city, and use that coordinate to retrieve the weather.
Getting weather from coordinate
Let us start by solving the first problem, how to retrieve the weather data from latitude and longitude. According to the OpenWeatherMap documentation, the API has the following pattern:
1https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}
If we call this API with the right parameters, lat
, lon
, and API key
, it should return us the corresponding weather data. For instance;
1import { useState, useEffect } from "react";
2
3function Weather() {
4 const [city, setCity] = useState("");
5 const [weatherData, setWeatherData] = useState(null);
6 const [error, setError] = useState("");
7
8 const apiKey = "<api_key>"; // Replace with your OpenWeatherMap API key
9
10 async function getWeatherByCoords(lat, lon) {
11 try {
12 const weatherResponse = await fetch(
13 `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric`
14 );
15
16 if (!weatherResponse.ok) {
17 throw new Error("Cannot find weather");
18 }
19
20 const weather = await weatherResponse.json();
21 setWeatherData(weather);
22 } catch (error) {
23 setError(error.message);
24 }
25 }
26
27 . . .
28
29 return (
30 <div style={{ textAlign: "center", marginTop: "50px" }}>
31 <h1>Weather App</h1>
32 <form onSubmit={handleSubmit}>
33 <input
34 type="text"
35 value={city}
36 onChange={(e) => setCity(e.target.value)}
37 placeholder="Enter city name"
38 />
39 <button type="submit">Get Weather</button>
40 </form>
41
42 {error && <p style={{ color: "red" }}>{error}</p>}
43
44 {weatherData && (
45 <div>
46 <h2>{weatherData.name}</h2>
47 <p>Temperature: {weatherData.main.temp} °C</p>
48 <p>Weather: {weatherData.weather[0].description}</p>
49 </div>
50 )}
51 </div>
52 );
53}
54
55export default Weather;
Pay attention to the getWeatherByCoords()
function:
1async function getWeatherByCoords(lat, lon) {
2 try {
3 const weatherResponse = await fetch(
4 `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric`
5 );
6
7 if (!weatherResponse.ok) {
8 throw new Error("Cannot find weather");
9 }
10
11 const weather = await weatherResponse.json();
12 setWeatherData(weather);
13 } catch (error) {
14 setError(error.message);
15 }
16}
From line 3 to 5, we utilize the built-in fetch()
function to make an API request to OpenWeatherMap. This function retrieves weather data from the external service.
Then, from line 7 to 9, we check the response status. If the status code indicates a failure (status code other than 2XX
), an error is thrown. The error will be caught by the try catch
block, and the state variable error
will be updated accordingly. The updated error
will trigger a rerender, and the error message will be displayed (line 42).
If the response is successful, weatherResponse.json()
parses the response body into JSON format. This data is then passed to setWeatherData
, which updates weatherData
with the retrieved weather data, and triggers a re-render (line 44 to 50).
Getting coordinate from city name
However, as discussed above, this getWeatherByCoords()
can not be used as the event handler directly, because it is unlikely for the user to know the exact coordinates. We'll need another function that wraps around getWeatherByCoords()
, and this function will be converting a city name into its corresponding coordinates.
OpenWeatherMap offers another tool for this purpose. The Geocoding API can be used to convert geographic names into coordinates, and vice versa.
1import { useState, useEffect } from "react";
2
3function Weather() {
4 const [city, setCity] = useState("");
5 const [weatherData, setWeatherData] = useState(null);
6 const [error, setError] = useState("");
7
8 const apiKey = "<api_key>"; // Replace with your OpenWeatherMap API key
9
10 . . .
11
12 async function getWeatherByCity(cityName) {
13 setError("");
14 setWeatherData(null);
15
16 try {
17 const coordinateResponse = await fetch(
18 `https://api.openweathermap.org/geo/1.0/direct?q=${cityName}&appid=${apiKey}`
19 );
20
21 if (!coordinateResponse.ok) {
22 throw new Error("City not found");
23 }
24
25 const coordinate = await coordinateResponse.json();
26 await getWeatherByCoords(coordinate[0].lat, coordinate[0].lon);
27 } catch (error) {
28 setError(error.message);
29 }
30 }
31
32 return (
33 <div style={{ textAlign: "center", marginTop: "50px" }}>
34 . . .
35 </div>
36 );
37}
38
39export default Weather;
In this example, the getWeatherByCity()
function accepts a city name, and use this city name to retrieve the coordinates via the Geocoding API.
The coordinates are then passed to getWeatherByCoords()
to retrieve the current weather at the requested location.
And lastly, we need an event handler. When the button is clicked, this handler should wrap up the user input and passed to the getWeatherByCity()
function:
1import { useState, useEffect } from "react";
2
3function Weather() {
4 const [city, setCity] = useState("");
5 const [weatherData, setWeatherData] = useState(null);
6 const [error, setError] = useState("");
7
8 const apiKey = "<api_key>"; // Replace with your OpenWeatherMap API key
9
10 . . .
11
12 function handleSubmit(e) {
13 e.preventDefault();
14 getWeatherByCity(city);
15 }
16
17 return (
18 <div style={{ textAlign: "center", marginTop: "50px" }}>
19 <h1>Weather App</h1>
20 <form onSubmit={handleSubmit}>
21 <input
22 type="text"
23 value={city}
24 onChange={(e) => setCity(e.target.value)}
25 placeholder="Enter city name"
26 />
27 <button type="submit">Get Weather</button>
28 </form>
29 . . .
30 </div>
31 );
32}
33
34export default Weather;
Display local weather using effects
Before we end this lesson, let's consider a more challenging problem. What if you want the weather app to detect the user's location and display the local weather when the app is first loaded?
To achieve this, you'll need to use side effects and the browser's Geolocation API.
We encourage you to try solving this problem first, but if you need some assistance, here is a demo:
1import { useState, useEffect } from "react";
2
3function Weather() {
4 const [city, setCity] = useState("");
5 const [weatherData, setWeatherData] = useState(null);
6 const [error, setError] = useState("");
7 const [userLocation, setUserLocation] = useState(null);
8
9 . . .
10
11 useEffect(() => {
12 if (!userLocation) {
13 navigator.geolocation.getCurrentPosition(
14 (position) => {
15 setUserLocation({
16 lat: position.coords.latitude,
17 lon: position.coords.longitude,
18 });
19 getWeatherByCoords(
20 position.coords.latitude,
21 position.coords.longitude
22 );
23 },
24 (err) => {
25 setError("An error occurred while retrieving location.");
26 }
27 );
28 }
29 }, [userLocation]);
30
31 return (
32 <div style={{ textAlign: "center", marginTop: "50px" }}>
33 . . .
34 </div>
35 );
36}
37
38export default Weather;