This is a step-by-step guide on how to build a simple weather application with React using AccuWeather API. You should have some basic knowledge of web development, such as HTML, CSS and JavaScript.
To create React applications you need to have node.js, npm (Node Package Manager) and code editor installed. Check the basics of React and how to set up required tools from here.
After installations, start by opening your chosen code editor. In this project I’m using Visual Studio Code. Open folder/workspace where you want to create your application. Then open a new terminal and create a new React project by typing next command to it:
npx create-react-app weatherapp
This creates a new folder that is named according to the project name. Then move to project:
cd weatherapp
Start the project with command:
npm start
This opens project to browser in the port 3000.
Open App.js file from the editor and delete everything from the first div element in the return statement.
Also delete the logo import line and the actual logo from the src
folder. Add imports for React and UseState Hook. Now your editor should look like this:
We use AccuWeather’s API in this project, so you have to get the free API key to be able to use it. You can get it from here.
Follow the instructions to register and log in. Once you have logged in you can find e.g. different API References, General Info and your Apps from the navigation.
Click My Apps and create a new App by clicking “Add a new App”. Fill following details:
Now you can find your application in My Apps. Click the created App and save your API Key for later use.
Note! You can only have one application at a time and the free version allows you to make up to 50 requests per day.
From API Reference link you can find all the different APIs that you can use. In this project we will use Locations API (City Search) and Current Conditions API (Current Conditions).
It’s not good to have the key coded in the same file where you use it, so create a new file for the key and url’s in the src
folder called constants.js
. Make variables for the apikey
and urls citysearch
and current
without the part of api key and location key, these will be added in the actual code. Then export these so you can use them in the App.js.
Import these in App.js:
import { apikey, citysearch, current } from "./constants.js";
We want to have a search field for the user to type the city they want to check the current weather. When user presses Enter, search
event invokes and with fetch
we get city’s location key from AccuWeather APi. Then that location key is being used in next fetch to get current conditions of that city.
Let’s create all necessary states inside function App:
const [city, setCity] = useState("");
const [details, setDetails] = useState({});
const [weather, setWeather] = useState({});
city
is for saving typed city name, details
is for saving objects from the first fetch (City Search) and weather
is for saving objects from the second fetch (Current Conditions).
Next add new div
for search field inside return
statement and <div className="App">
and add following attributes for it:
<div className="search">
<input
className="search-field"
placeholder="Enter city.."
value={city}
onChange={(e) => setCity(e.target.value)}
onKeyPress={search}
/>
</div>
Input's value is the city
state which is saved with onChange
event as the user is typing city name to the input field. onKeyPress
event triggers the search
function.
Next we create the search
function:
const search = (evt) => {
if (evt.key === "Enter") {
fetch(`${citysearch}cities/search?apikey=${apikey}&q=${city}`)
.then((res) => res.json())
.then((result) => {
console.log(result[0].Key);
setDetails(result[0]);
})
}
};
If the pressed key is Enter then code will execute the fetch. We will use the variables from the constants.js
and previously saved city
state in the fetched url. Result is changed into JSON form. Information we need is in the object's first index (0) and we can save them to details
state with setDetails
for later use. We are printing the location key with console.log
to be sure that we actually get the key.
Next we continue with another fetch to get current conditions. Now we use the current
url and add the result[0].Key
and apikey
in the fetch. Again the result is changed into JSON form and the first index of the object is being saved in the weather
state with setWeather
. We can add printing to see what fetched data contains. We also add error catching which prints possible errors in the browser’s console.
Here is the whole search function:
const search = (evt) => { if (evt.key === "Enter") { fetch(`${citysearch}cities/search?apikey=${apikey}&q=${city}`) .then((res) => res.json()) .then((result) => { console.log(result[0].Key); setDetails(result[0]); fetch(`${current}${result[0].Key}?apikey=${apikey}`) .then((res) => res.json()) .then((result) => { setWeather(result[0]); console.log(result[0]); }) .catch((err) => { console.error(err); }); }); } };
Fetched data in the console:
Now that we have successfully fetched the data we want, we can start creating UI for it. For this we need to create elements for different information. I created everything inside <main>
and one big div
called box which contains following divs:
- top-box: welcome and instruction texts
- search: search-field
- location: cityname, date and time
- weather: temperature, conditions and weather icon (this will be handled later)
In the code:
In the browser:
Now all the information are just hard-coded, so let’s display the actual data we fetched.
We can display data with JSX (curly brackets). We got the cityname and country from the first fetch (City Search), we just have to know the right keywords to get them. You can find those from AccuWeather’s API Reference documentation. For the cityname we use LocalizedName
and for the country Country.LocalizedName
. Since we saved them in details
state we just have to use it front of them.
<div>
<div className="location">
<div className="cityname">{details.LocalizedName}, {details.Country.LocalizedName}</div>
<div className="date">1.1.2022</div>
<div className="time">13:13:00</div>
</div>
</div>
Now if you check the browser, you can see that there is only a blank page. That’s because we haven’t fetched anything yet, so the properties of the details
are "undefined". We have to make some changes for the code to check if the fetch has been made.
We add some if-else sentence for the <div>
to see if details property Key is not "undefined". If sentence is true (details.Key
is defined), the page shows everything inside the <div>
. If false then page only renders empty space but the page itself doesn’t crash.
{typeof details.Key != "undefined" ? (
<div>
<div className="location">
<div className="cityname">{details.LocalizedName}, {details.Country.LocalizedName}</div>
<div className="date">1.1.2022</div>
<div className="time">13:13:00</div>
</div>
</div>
) : (" ")}
Before fetching:
After fetching:
Now let’s do the same with weather div
. Now the data we want to display is from the second fetch and is saved in weather
state. That’s why we have to use some property of weather state in the if-else sentence. I have used WeatherText
. We also want to show the temperature rounded so it doesn’t show any decimals. For that we can use Math.round
function which returns the value of a number to the nearest integer.
{typeof weather.WeatherText != "undefined" ? (
<div>
<div className="weather">
<div className="temperature">
{Math.round(weather.Temperature.Metric.Value)}°C
</div>
<div className="conditions">
{weather.WeatherText}
</div>
<div className="icon-box">
<img className="icon" src="" alt="weathericon" />
</div>
</div>
</div>
) : (" ")}
There are multiple ways to get date and time in Javascript but here I have made my own Datebuilder
function for date and getTime
function for time. They use Javascript Date object’s methods.
We create lists for month names and weekday names so instead of just getting numbers we can get name of current day and month. We also want to display day number and full year so we save all those in different variables and then return them.
const dateBuilder = (d) => {
let months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
let days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
let day = days[d.getDay()];
let date = d.getDate();
let month = months[d.getMonth()];
let year = d.getFullYear();
return `${day} ${date} ${month} ${year}`;
};
We create one variable time
that contains hours, minutes and seconds that are separated with :
. In the end we return time
variable.
const getTime = () => {
let today = new Date();
const time =
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
return `${time}`;
};
When displaying date and time, we only have to call functions inside curly brackets with new Date()
. I chose to combine both date and time in one div, so now the time div
is useless and can be removed.
{typeof details.Key != "undefined" ? (
<div>
<div className="location">
<div className="cityname">{details.LocalizedName}, {details.Country.LocalizedName}</div>
<div className="date">{dateBuilder(new Date())} {getTime(new Date())}</div>
</div>
</div>
) : (" ")}
Browser:
In AccuWeather’s API page you can find numbered icons for different weathers. You can’t make requests for that page, but when fetching Current Conditions, the result shows WeatherIcon number. I created a new folder, icons
, in public
folder where I saved all the icons and named them with number that equals the one found on the website, e.g. 1.png, 2.png... This is a bit of hard work and is not mandatory if you don’t want to use icons on in your application.
To use these icons, we have to make a new state for the icon’s source iconSrc
. It is set in the second fetch with the folder path, result’s WeatherIcon number and ended with .png.
function App() { const [city, setCity] = useState(""); const [details, setDetails] = useState({}); const [weather, setWeather] = useState({}); const [iconSrc, setIconSrc] = useState(""); const search = (evt) => { if (evt.key === "Enter") { fetch(`${citysearch}cities/search?apikey=${apikey}&q=${city}`) .then((res) => res.json()) .then((result) => { console.log(result[0].Key); setDetails(result[0]); fetch(`${current}${result[0].Key}?apikey=${apikey}`) .then((res) => res.json()) .then((result) => { setWeather(result[0]); setIconSrc(`/icons/${result[0].WeatherIcon}.png`); console.log(result[0].WeatherIcon); console.log(result[0]); }) .catch((err) => { console.error(err); }); }); } };
Now we can use only {iconSrc}
as the source when displaying the icon.
<div className="icon-box">
<img className="icon" src={iconSrc} alt="weathericon" />
</div>
Last thing before touching CSS, I wanted to add few backgrounds that change depending on temperature or if it’s day or night. I searched suitable images from pixabay and added them to same folder with the icons. I wanted to display summer scene as default and if it’s daytime or temperature is over 0. If on the other hand it’s nighttime, a night scene is shown, and if temperature is below 0 then a nice snowy scene is displayed.
Default summer scene:
Night scene:
Winter scene:
Now let’s move on to modifying App.css
.
When project is created it automatically adds two css files: App.css
and index.css
. We use App.css
in this project since we only use one component. Open App.css
and delete all default settings.
For the changing background, I created three different versions of App
class. Only difference between them is the background-image.
.App {
background-image: url('/public/icons/summerday.png');
background-size: inherit;
background-position: center;
background-repeat: no-repeat;
}
.App.night {
background-image: url('/public/icons/night.png');
}
.App.winter {
background-image: url('/public/icons/winterday.jpg');
}
You can see the default summer scene now in the browser:
Now since we want to have changing background, we have to do some modifications to App.js
. We add similar if-else sentence as before but a bit more complex version to the first div
element named App
.
Line 77: First we have to check if we have fetched anything, if not then we just jump straight to line 83 and show the default image.
Line 78: If we have fetched, then check if it’s daytime (this information can also be found from the second fetch result). If not then move to line 82 and show the night image.
Line 79: If it is indeed daytime then check if temperature is below 0, if true move to line 80 and show winter image and if not then move to line 81 and show the default image of summer.
We can check if program works right by changing the temperature check mark for greater than 0, so the winter image is displayed:
Daytime is harder to test since our application only works in Finland and can’t then make search for example some city in other side of the world where it actually is night. But I have tested that also by staying up late and running the code at night :D
I’ve added some basic values for margins, paddings, elements heights and widths etc. I’m not going to go through these in very detail since it’s only for the visual part and doesn’t affect functionality. I wanted all texts to be white and have some shadow to get some contrast and get that app look.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
main {
padding: 50px;
min-height: 100vh;
}
.top-box .header {
color: #FFF;
font-size: 60px;
font-weight: 500;
text-align: center;
text-shadow: 2px 2px rgba(50, 50, 70, 0.5);
padding: 25px;
padding-bottom: 30px;
}
.top-box .start {
color: #FFF;
font-size: 25px;
font-weight: 500;
text-align: center;
text-shadow: 2px 2px rgba(50, 50, 70, 0.5);
padding: 25px;
padding-bottom: 15px;
}
For the search field I added bit transparency and set some default values to none, e.g. border. I also added some shadow and rounded the corners.
.search {
width: 100%;
margin: 0 0 75px;
}
.search .search-field {
display: block;
margin: 0 auto;
width: 30%;
padding: 15px;
appearance: none;
border: none;
outline: none;
background-color: rgb(255, 255, 255, 0.9);
border-radius: 10px 10px 10px 10px;
box-shadow: 0px 5px rgba(0, 0, 0, 0.2);
color: #313131;
font-size: 20px;
}
I wanted to display temperature in an own box so it stands out better from the background so I added background-color with transparency and displayed it in inline-block so the box only contains the numbers and doesn’t stretch to whole page wide.
.weather {
text-align: center;
}
.weather .temperature {
position: relative;
display: inline-block;
margin: 30px auto;
background-color: rgba(255, 255, 255, 0.3);
padding: 15px 25px;
color: #FFF;
font-size: 100px;
font-weight: 900;
text-shadow: 3px 6px rgba(29, 29, 34, 0.7);
text-align: center;
box-shadow: 3px 6px rgba(0, 0, 0, 0.2);
}
Lastly, I wanted to add a light gray background when data is displayed so it stands out from all backgrounds. The winter image is quite white, so it’s a bit hard to read all texts.
So I added some CSS for the div box
that contains all texts when it is visible.
.box {
position: relative;
margin: auto;
width: 70%;
height: 90%;
border-radius: 10px 10px 10px 10px;
background-color: rgba(160, 160, 160, 0.15);
}
This needed some changes in App.js
to work correctly. I added div below main tag and made again if-else sentence. It checks if fetch has been made and if so, it shows the box. If not, then the box doesn’t show.
<main>
<div className={typeof weather.WeatherText != "undefined" ? "box" : ""}>
Note! Remember to add the ending tag in the end before end of main tag.
Before fetch:
After fetch:
That's it! Now your application is ready for use.
Note! Now the application only runs on your local device. I have deployed my weather app to GitHub pages and it requires some changes in the program. If you wish to do the same, you can follow instruction from here. You also have to change code and use environment variable for the icons since program can’t find them anymore. Here is some more info about that.
The changed code looks like this:
<img className="icon" src={process.env.PUBLIC_URL + iconSrc} alt="weathericon" />