Introduction
Welcome to our comprehensive guide on creating a Weather App application using React. Today We’ll be creating a Simple interactive Weather App Using ReactJS. ReactJs is a JavaScript Framework made by Facebook. We will be working with Class Based Components in this application and use the React-Bootstrap module to style the components. You’ll acquire the knowledge of Hooks in ReactJS.
Preview Of Weather App using ReactJS

PreRequistics to Start Weather App
- Basic Knowledge of HTML
- Basic Knowledge of CSS
- Basic Knowledge of JavaScript including ReactJS Concepts.
Set Up The Environment For Weather App
Let’s create a new React application using the create-react-app CLI tool:
Copy the following command and run it in your CMD
$ npx create-react-app weather-app
$ cd react-weather
$ npm install --save [email protected] [email protected]
$ npm start
This will gonna create the weather-app application in your desired file folder and Now open in your Favourite Code editor e.g. Vs Code. You’ll see the output on the browser like below:-

Steps to Create a Weather Application
The basic Folder Structure for our Weather App is Provided below make sure to create the files and folder. Inside the Components Directory Create the Following Folders:-

Create the Below file inside your API folder:-

OpenWeatherService.js
const GEO_API_URL = 'https://wft-geo-db.p.rapidapi.com/v1/geo';
const WEATHER_API_URL = 'https://api.openweathermap.org/data/2.5';
const WEATHER_API_KEY = 'Your API KEY';
const GEO_API_OPTIONS = {
method: 'GET',
headers: {
'X-RapidAPI-Key': '4f0dcce84bmshac9e329bd55fd14p17ec6fjsnff18c2e61917',
'X-RapidAPI-Host': 'wft-geo-db.p.rapidapi.com',
},
};
export async function fetchWeatherData(lat, lon) {
try {
let [weatherPromise, forcastPromise] = await Promise.all([
fetch(
`${WEATHER_API_URL}/weather?lat=${lat}&lon=${lon}&appid=${WEATHER_API_KEY}&units=metric`
),
fetch(
`${WEATHER_API_URL}/forecast?lat=${lat}&lon=${lon}&appid=${WEATHER_API_KEY}&units=metric`
),
]);
const weatherResponse = await weatherPromise.json();
const forcastResponse = await forcastPromise.json();
return [weatherResponse, forcastResponse];
} catch (error) {
console.log(error);
}
}
export async function fetchCities(input) {
try {
const response = await fetch(
`${GEO_API_URL}/cities?minPopulation=10000&namePrefix=${input}`,
GEO_API_OPTIONS
);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
return;
}
}
Inside the Reusable Folder Create the Following File and Add the Code:-

ErrorBox.js
import * as React from 'react';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import Box from '@mui/material/Box';
import { Typography } from '@mui/material';
export default function ErrorBox(props) {
return (
<Box
display={props.display || 'flex'}
justifyContent={props.justifyContent || 'center'}
alignItems={props.alignItems || 'center'}
margin={props.margin || '1rem auto'}
gap={props.gap || '8px'}
flex={props.flex || 'auto'}
width={props.width || 'auto'}
sx={{
padding: '1rem',
flexDirection: { xs: 'column', sm: 'row' },
color: props.type === 'info' ? '#f5a922' : '#DC2941',
border:
props.type === 'info' ? '1px solid #f5a922' : '1px solid #DC2941',
borderRadius: '8px',
background:
props.type === 'info'
? 'rgba(245, 169, 34, .1)'
: 'rgba(220, 41, 65, .25)',
}}
>
<ErrorOutlineIcon sx={{ fontSize: '24px' }} />
<Typography
variant="h2"
component="h2"
sx={{
fontSize:
props.type === 'info'
? { xs: '12px', sm: '14px' }
: { xs: '14px', sm: '16px' },
fontFamily: 'Poppins',
textAlign: 'center',
}}
>
{props.errorMessage || 'Internal error'}
</Typography>
</Box>
);
}
Layout.js
import { Grid } from '@mui/material';
import React from 'react';
import SectionHeader from './SectionHeader';
const Layout = ({ content, title, sx, mb, sectionSubHeader }) => {
return (
<Grid container sx={sx}>
<Grid item xs={12}>
<SectionHeader title={title} mb={mb || '0'} />
{sectionSubHeader || null}
</Grid>
{content}
</Grid>
);
};
export default Layout;
LoadingBox.js
import * as React from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
export default function LoadingBox(props) {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
alignItems: 'center',
gap: '1rem',
}}
>
<CircularProgress sx={{ color: 'rgba(255,255,255, .8)' }} />
{props.children}
</Box>
);
}
SectionHeader.js
import { Typography } from '@mui/material';
import React from 'react';
const SectionHeader = ({ title, mb }) => {
return (
<Typography
variant="h5"
component="h5"
sx={{
fontSize: { xs: '12px', sm: '16px', md: '18px' },
color: 'rgba(255,255,255,.7)',
fontWeight: '600',
lineHeight: 1,
textAlign: 'center',
fontFamily: 'Roboto Condensed',
marginBottom: mb ? mb : '1rem',
}}
>
{title}
</Typography>
);
};
export default SectionHeader;
UTCDateTime.js
import { Typography } from '@mui/material';
import React from 'react';
import { getUTCDatetime } from '../../utilities/DatetimeUtils';
const UTCDatetime = () => {
const utcFullDate = getUTCDatetime();
const utcTimeValue = (
<Typography
variant="h3"
component="h3"
sx={{
fontWeight: '400',
fontSize: { xs: '10px', sm: '12px' },
color: 'rgba(255, 255, 255, .7)',
lineHeight: 1,
paddingRight: '2px',
fontFamily: 'Poppins',
}}
>
{utcFullDate} GMT
</Typography>
);
return utcTimeValue;
};
export default UTCDatetime;
Create Another File inside the Search Folder as below:-
Search.js
import React, { useState } from 'react';
import { AsyncPaginate } from 'react-select-async-paginate';
import { fetchCities } from '../../api/OpenWeatherService';
const Search = ({ onSearchChange }) => {
const [searchValue, setSearchValue] = useState(null);
const loadOptions = async (inputValue) => {
const citiesList = await fetchCities(inputValue);
return {
options: citiesList.data.map((city) => {
return {
value: `${city.latitude} ${city.longitude}`,
label: `${city.name}, ${city.countryCode}`,
};
}),
};
};
const onChangeHandler = (enteredData) => {
setSearchValue(enteredData);
onSearchChange(enteredData);
};
return (
<AsyncPaginate
placeholder="Search for cities"
debounceTimeout={600}
value={searchValue}
onChange={onChangeHandler}
loadOptions={loadOptions}
/>
);
};
export default Search;
Move to Another Folder TodayWeather and Add the Following Files and Folder:-

Your Folder structure should look like the below one:-

Airconditions.js
import React from 'react';
import ErrorBox from '../../Reusable/ErrorBox';
import AirConditionsItem from './AirConditionsItem';
import Layout from '../../Reusable/Layout';
const TodayWeatherAirConditions = ({ data }) => {
const noDataProvided =
!data || Object.keys(data).length === 0 || data.cod === '404';
let content = <ErrorBox flex="1" type="error" />;
if (!noDataProvided)
content = (
<>
<AirConditionsItem
title="Real Feel"
value={`${Math.round(data.main.feels_like)} °C`}
type="temperature"
/>
<AirConditionsItem
title="Wind"
value={`${data.wind.speed} m/s`}
type="wind"
/>
<AirConditionsItem
title="Clouds"
value={`${Math.round(data.clouds.all)} %`}
type="clouds"
/>
<AirConditionsItem
title="Humidity"
value={`${Math.round(data.main.humidity)} %`}
type="humidity"
/>
</>
);
return (
<Layout
title="AIR CONDITIONS"
content={content}
mb="1rem"
sx={{ marginTop: '2.9rem' }}
/>
);
};
export default TodayWeatherAirConditions;
AirconditionsItem.js
import { Box, Grid, SvgIcon } from '@mui/material';
import React from 'react';
import ThermostatIcon from '@mui/icons-material/Thermostat';
import AirIcon from '@mui/icons-material/Air';
import FilterDramaIcon from '@mui/icons-material/FilterDrama';
import { ReactComponent as HumidityIcon } from '../../../assets/humidity.svg';
const AirConditionsItem = (props) => {
let iconContent;
if (props.type === 'temperature')
iconContent = <ThermostatIcon sx={{ fontSize: '18px' }} />;
else if (props.type === 'wind')
iconContent = <AirIcon sx={{ fontSize: '18px' }} />;
else if (props.type === 'clouds')
iconContent = <FilterDramaIcon sx={{ fontSize: '18px' }} />;
else if (props.type === 'humidity')
iconContent = (
<SvgIcon
component={HumidityIcon}
inheritViewBox
sx={{ fontSize: '18px' }}
/>
);
return (
<Grid
item
xs={3}
sx={{
padding: '0',
height: '80px',
}}
>
<Grid
item
xs={12}
display="flex"
alignItems="center"
justifyContent="center"
sx={{
width: '100%',
height: '40px',
flexDirection: { xs: 'column', sm: 'row' },
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
color: 'rgba(255, 255, 255, .7)',
padding: 0,
}}
>
{iconContent}
</Box>
<Box
sx={{
color: 'rgba(255, 255, 255, .7)',
fontSize: { xs: '10px', sm: '12px', md: '14px' },
paddingLeft: { xs: '0px', sm: '4px', md: '6px' },
paddingTop: { xs: '2px', sm: '0px' },
display: 'flex',
alignItems: 'center',
}}
>
{props.title}
</Box>
</Grid>
<Grid
item
xs={12}
display="flex"
alignItems="center"
justifyContent="center"
sx={{ height: '40px' }}
>
<Box
sx={{
fontFamily: 'Poppins',
fontWeight: '600',
fontSize: { xs: '12px', sm: '14px', md: '16px' },
color: 'white',
lineHeight: 1,
}}
>
{props.value}
</Box>
</Grid>
</Grid>
);
};
export default AirConditionsItem;
CityDateDetails.js
import { Box, Typography } from '@mui/material';
import React from 'react';
const CityDateDetail = (props) => {
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
height: '100%',
}}
>
<Typography
variant="h3"
component="h3"
sx={{
fontFamily: 'Poppins',
fontWeight: '600',
fontSize: { xs: '12px', sm: '14px', md: '16px' },
color: 'white',
textTransform: 'uppercase',
lineHeight: 1,
marginBottom: '8px',
}}
>
{props.city}
</Typography>
<Typography
variant="h4"
component="h4"
sx={{
fontSize: { xs: '10px', sm: '12px', md: '14px' },
color: 'rgba(255,255,255, .7)',
lineHeight: 1,
letterSpacing: { xs: '1px', sm: '0' },
fontFamily: 'Roboto Condensed',
}}
>
Today {props.date}
</Typography>
</Box>
);
};
export default CityDateDetail;
Details.js
import React from 'react';
import { Grid } from '@mui/material';
import { getDayMonthFromDate } from '../../../utilities/DatetimeUtils';
import { weatherIcon } from '../../../utilities/IconsUtils';
import ErrorBox from '../../Reusable/ErrorBox';
import CityDateDetail from './CityDateDetail';
import TemperatureWeatherDetail from './TemperatureWeatherDetail';
import WeatherIconDetail from './WeatherIconDetail';
import Layout from '../../Reusable/Layout';
const dayMonth = getDayMonthFromDate();
const Details = ({ data }) => {
const noDataProvided =
!data || Object.keys(data).length === 0 || data.cod === '404';
let content = <ErrorBox flex="1" type="error" />;
if (!noDataProvided)
content = (
<>
<Grid
item
xs={4}
sx={{
height: '80px',
}}
>
<CityDateDetail city={data.city} date={dayMonth} />
</Grid>
<Grid
item
xs={4}
sx={{
height: '80px',
}}
>
<TemperatureWeatherDetail
temperature={data.main.temp}
description={data.weather[0].description}
/>
</Grid>
<Grid
item
xs={4}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '80px',
}}
>
<WeatherIconDetail src={weatherIcon(`${data.weather[0].icon}.png`)} />
</Grid>
</>
);
return <Layout title="CURRENT WEATHER" content={content} />;
};
export default Details;
TemperatureWeatherDetail.js
import { Box, Typography } from '@mui/material';
import React from 'react';
const TemperatureWeatherDetail = (props) => {
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
height: '100%',
}}
>
<Typography
variant="h3"
component="h3"
sx={{
fontWeight: '600',
fontSize: { xs: '12px', sm: '14px', md: '16px' },
color: 'white',
textTransform: 'uppercase',
lineHeight: 1,
marginBottom: '8px',
fontFamily: 'Poppins',
}}
>
{Math.round(props.temperature)} °C
</Typography>
<Typography
variant="h4"
component="h4"
sx={{
fontSize: { xs: '10px', sm: '12px', md: '14px' },
color: 'rgba(255,255,255, .7)',
lineHeight: 1,
letterSpacing: { xs: '1px', sm: '0' },
fontFamily: 'Roboto Condensed',
}}
>
{props.description}
</Typography>
</Box>
);
};
export default TemperatureWeatherDetail;
WeatherIconDetail.js
import { Box } from '@mui/material';
import React from 'react';
const WeatherIconDetail = (props) => {
return (
<Box
component="img"
sx={{
width: { xs: '50px', sm: '60px' },
height: 'auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
margin: '0 auto',
padding: '0',
}}
alt="weather"
src={props.src}
/>
);
};
export default WeatherIconDetail;
Inside the Forcast Folder Add the following Files:-
DailyForecast.js
import React from 'react';
import { Grid, Typography } from '@mui/material';
import DailyForecastItem from './DailyForecastItem';
import ErrorBox from '../../Reusable/ErrorBox';
import Layout from '../../Reusable/Layout';
const DailyForecast = ({ data, forecastList }) => {
const noDataProvided =
!data ||
!forecastList ||
Object.keys(data).length === 0 ||
data.cod === '404' ||
forecastList.cod === '404';
let subHeader;
if (!noDataProvided && forecastList.length > 0)
subHeader = (
<Typography
variant="h5"
component="h5"
sx={{
fontSize: { xs: '10px', sm: '12px' },
textAlign: 'center',
lineHeight: 1,
color: '#04C4E0',
fontFamily: 'Roboto Condensed',
marginBottom: '1rem',
}}
>
{forecastList.length === 1
? '1 available forecast'
: `${forecastList.length} available forecasts`}
</Typography>
);
let content;
if (noDataProvided) content = <ErrorBox flex="1" type="error" />;
if (!noDataProvided && forecastList.length > 0)
content = (
<Grid
item
container
xs={12}
sx={{
display: 'flex',
justifyContent: 'center',
width: 'fit-content',
}}
spacing="4px"
>
{forecastList.map((item, idx) => (
<Grid
key={idx}
item
xs={4}
sm={2}
display="flex"
flexDirection="column"
alignItems="center"
sx={{
marginBottom: { xs: '1rem', sm: '0' },
}}
>
<DailyForecastItem item={item} data={data} />
</Grid>
))}
</Grid>
);
if (!noDataProvided && forecastList && forecastList.length === 0)
subHeader = (
<ErrorBox
flex="1"
type="info"
margin="2rem auto"
errorMessage="No available forecasts for tonight."
/>
);
return (
<Layout
title="TODAY'S FORECAST"
content={content}
sectionSubHeader={subHeader}
sx={{ marginTop: '2.9rem' }}
mb="0.3rem"
/>
);
};
export default DailyForecast;
DailyForecastItem.js
import { Box, Typography } from '@mui/material';
import React from 'react';
import { weatherIcon } from '../../../utilities/IconsUtils';
const DailyForecastItem = (props) => {
return (
<Box
sx={{
background:
'linear-gradient(0deg, rgba(255, 255, 255, .05) 0%, rgba(171, 203, 222, .05) 100%) 0% 0%',
borderRadius: '8px',
boxShadow:
'rgba(0, 0, 0, 0.05) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
textAlign: 'center',
padding: '4px 0',
width: '100%',
}}
>
<Typography
variant="h3"
component="h3"
sx={{
fontWeight: '400',
fontSize: { xs: '10px', sm: '12px' },
color: 'rgba(255, 255, 255, .7)',
lineHeight: 1,
padding: '4px',
fontFamily: 'Poppins',
}}
>
{props.item.time}
</Typography>
<Box
sx={{
display: 'flex',
alignItems: 'center',
color: 'white',
padding: '4px',
}}
>
<Box
component="img"
sx={{
width: { xs: '36px', sm: '42px' },
height: 'auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
margin: '0 auto',
}}
alt="weather"
src={weatherIcon(`${props.data.weather[0].icon}.png`)}
/>
</Box>
<Typography
variant="h3"
component="h3"
sx={{
fontWeight: '600',
fontSize: { xs: '12px', sm: '14px' },
color: 'white',
textTransform: 'uppercase',
lineHeight: 1,
marginBottom: { xs: '8px', md: '0' },
fontFamily: 'Poppins',
}}
>
{props.item.temperature}
</Typography>
</Box>
);
};
export default DailyForecastItem;
TodayWeather.js
import { Grid } from '@mui/material';
import React from 'react';
import AirConditions from './AirConditions/AirConditions';
import DailyForecast from './Forecast/DailyForecast';
import Details from './Details/Details';
const TodayWeather = ({ data, forecastList }) => {
return (
<Grid container sx={{ padding: '3rem 0rem 0rem' }}>
<Details data={data} />
<AirConditions data={data} />
<DailyForecast data={data} forecastList={forecastList} />
</Grid>
);
};
export default TodayWeather;
Create Followings Files for the WeeklyForeCast Folder

DayWeatherDetails.js
import { Box, Grid, Typography } from '@mui/material';
import React from 'react';
const DayWeatherDetails = (props) => {
return (
<Grid
container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
paddingLeft: { xs: '12px', sm: '20px', md: '32px' },
}}
>
<Typography
xs={12}
sx={{
fontFamily: 'Poppins',
fontWeight: { xs: '400', sm: '600' },
fontSize: { xs: '12px', sm: '13px', md: '14px' },
color: 'white',
lineHeight: 1,
height: '31px',
alignItems: 'center',
display: 'flex',
}}
>
{props.day}
</Typography>
<Box
xs={12}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '31px',
}}
>
<Box
component="img"
sx={{
width: { xs: '24px', sm: '28px', md: '31px' },
height: 'auto',
marginRight: '4px',
}}
alt="weather"
src={props.src}
/>
<Typography
variant="h4"
component="h4"
sx={{
fontSize: { xs: '12px', md: '14px' },
color: 'rgba(255,255,255, .8)',
lineHeight: 1,
fontFamily: 'Roboto Condensed',
}}
>
{props.description}
</Typography>
</Box>
</Grid>
);
};
export default DayWeatherDetails;
UnfedForCastItem.js
import { Box, Grid, Typography } from '@mui/material';
import React from 'react';
import WeeklyForecastItem from './WeeklyForecastItem';
const UnfedForecastItem = (props) => {
return (
<>
<Grid
container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
paddingLeft: { xs: '12px', sm: '20px', md: '32px' },
}}
>
<Typography
xs={12}
sx={{
fontFamily: 'Poppins',
fontWeight: { xs: '400', sm: '600' },
fontSize: { xs: '12px', sm: '13px', md: '14px' },
color: 'white',
lineHeight: 1,
height: '31px',
alignItems: 'center',
display: 'flex',
}}
>
{props.day}
</Typography>
<Box
xs={12}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '31px',
}}
>
<Box
component="img"
sx={{
width: { xs: '24px', sm: '28px', md: '31px' },
height: 'auto',
marginRight: '4px',
}}
alt="weather"
src={props.src}
/>
<Typography
variant="h4"
component="h4"
sx={{
fontSize: { xs: '12px', md: '14px' },
color: 'rgba(255,255,255, .8)',
lineHeight: 1,
fontFamily: 'Roboto Condensed',
}}
>
{props.value}
</Typography>
</Box>
</Grid>
<Grid
container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
>
<WeeklyForecastItem
type="temperature"
value={props.value}
color="black"
/>
<WeeklyForecastItem type="clouds" value={props.value} color="black" />
</Grid>
<Grid
container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
>
<WeeklyForecastItem type="wind" value={props.value} color="green" />
<WeeklyForecastItem type="humidity" value={props.value} color="green" />
</Grid>
</>
);
};
export default UnfedForecastItem;
WeeklyForecast.js
import React from 'react';
import { Grid } from '@mui/material';
import { getWeekDays } from '../../utilities/DatetimeUtils';
import { weatherIcon } from '../../utilities/IconsUtils';
import WeeklyForecastItem from './WeeklyForecastItem';
import ErrorBox from '../Reusable/ErrorBox';
import UnfedForecastItem from './UnfedForecastItem';
import DayWeatherDetails from './DayWeatherDetails';
import Layout from '../Reusable/Layout';
const WeeklyForecast = ({ data }) => {
const forecastDays = getWeekDays();
const noDataProvided =
!data ||
Object.keys(data).length === 0 ||
!data.list ||
data.list.length === 0;
let content = (
<div style={{ width: '100%' }}>
<ErrorBox type="error" />
</div>
);
if (!noDataProvided)
content = (
<Grid
item
container
display="flex"
flexDirection="column"
xs={12}
gap="4px"
>
{data.list.map((item, idx) => {
return (
<Grid
item
key={idx}
xs={12}
display="flex"
alignItems="center"
sx={{
padding: '2px 0 2px',
background:
'linear-gradient(0deg, rgba(255, 255, 255, .05) 0%, rgba(171, 203, 222, .05) 100%) 0% 0%',
boxShadow:
'rgba(0, 0, 0, 0.05) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
borderRadius: '8px',
}}
>
<DayWeatherDetails
day={forecastDays[idx]}
src={weatherIcon(`${item.icon}`)}
description={item.description}
/>
<Grid
container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
>
<WeeklyForecastItem
type="temperature"
value={Math.round(item.temp) + ' °C'}
color="black"
/>
<WeeklyForecastItem
type="clouds"
value={item.clouds + ' %'}
color="black"
/>
</Grid>
<Grid
container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
>
<WeeklyForecastItem
type="wind"
value={item.wind + ' m/s'}
color="green"
/>
<WeeklyForecastItem
type="humidity"
value={item.humidity + ' %'}
color="green"
/>
</Grid>
</Grid>
);
})}
{data.list.length === 5 && (
<Grid
item
xs={12}
display="flex"
alignItems="center"
sx={{
padding: '2px 0 2px',
background:
'linear-gradient(0deg, rgba(255, 255, 255, .05) 0%, rgba(171, 203, 222, .05) 100%) 0% 0%',
boxShadow:
'rgba(0, 0, 0, 0.05) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
borderRadius: '8px',
}}
>
<UnfedForecastItem
day={forecastDays[5]}
value="NaN"
src={weatherIcon('unknown.png')}
/>
</Grid>
)}
</Grid>
);
return (
<Layout
title="WEEKLY FORECAST"
content={content}
mb=".8rem"
sx={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'center',
padding: '3rem 0 0',
}}
/>
);
};
export default WeeklyForecast;
WeeklyForecastitem.js
import React from 'react';
import { Box, SvgIcon, Typography } from '@mui/material';
import AirIcon from '@mui/icons-material/Air';
import FilterDramaIcon from '@mui/icons-material/FilterDrama';
import ThermostatIcon from '@mui/icons-material/Thermostat';
import { ReactComponent as HumidityIcon } from '../../assets/humidity.svg';
const WeeklyForecastItem = ({ value, type }) => {
let iconContent;
if (type === 'temperature')
iconContent = (
<ThermostatIcon
sx={{ fontSize: { xs: '15px', sm: '16px', md: '18px' } }}
/>
);
else if (type === 'wind')
iconContent = (
<AirIcon sx={{ fontSize: { xs: '15px', sm: '16px', md: '18px' } }} />
);
else if (type === 'clouds')
iconContent = (
<FilterDramaIcon
sx={{ fontSize: { xs: '15px', sm: '16px', md: '18px' } }}
/>
);
else if (type === 'humidity')
iconContent = (
<SvgIcon
component={HumidityIcon}
inheritViewBox
sx={{
fontSize: { xs: '15px', sm: '16px', md: '18px' },
}}
/>
);
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '31px',
color: 'rgba(255, 255, 255, .7)',
gap: { xs: '3px', sm: '4px', md: '6px' },
width: '100%',
}}
>
{iconContent}
<Typography
variant="p"
component="p"
sx={{
fontSize: { xs: '12px', sm: '13px' },
fontWeight: { xs: '400', sm: '600' },
color: 'white',
fontFamily: 'Poppins',
lineHeight: 1,
}}
>
{value}
</Typography>
</Box>
);
};
export default WeeklyForecastItem;
Create Files inside the utilities folder. Your folder Folder Structure should look like this:-

ApiService.js
const GEO_API_URL = 'https://wft-geo-db.p.rapidapi.com/v1/geo';
const WEATHER_API_URL = 'https://api.openweathermap.org/data/2.5';
const WEATHER_API_KEY = 'YOUR API KEY';
const GEO_API_OPTIONS = {
method: 'GET',
headers: {
'X-RapidAPI-Key': '4f0dcce84bmshac9e329bd55fd14p17ec6fjsnff18c2e61917',
'X-RapidAPI-Host': 'wft-geo-db.p.rapidapi.com',
},
};
export async function fetchWeatherData(lat, lon) {
try {
let [weatherPromise, forcastPromise] = await Promise.all([
fetch(
`${WEATHER_API_URL}/weather?lat=${lat}&lon=${lon}&appid=${WEATHER_API_KEY}&units=metric`
),
fetch(
`${WEATHER_API_URL}/forecast?lat=${lat}&lon=${lon}&appid=${WEATHER_API_KEY}&units=metric`
),
]);
const weatherResponse = await weatherPromise.json();
const forcastResponse = await forcastPromise.json();
return [weatherResponse, forcastResponse];
} catch (error) {
console.log(error);
}
}
export async function fetchCities(input) {
try {
const response = await fetch(
`${GEO_API_URL}/cities?minPopulation=10000&namePrefix=${input}`,
GEO_API_OPTIONS
);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
return;
}
}
DataUtils.js
export function groupBy(key) {
return function group(array) {
return array.reduce((acc, obj) => {
const property = obj[key];
const { date, ...rest } = obj;
acc[property] = acc[property] || [];
acc[property].push(rest);
return acc;
}, {});
};
}
export function getAverage(array, isRound = true) {
let average = 0;
if (isRound) {
average = Math.round(array.reduce((a, b) => a + b, 0) / array.length);
if (average === 0) {
average = 0;
}
} else average = (array.reduce((a, b) => a + b, 0) / array.length).toFixed(2);
return average;
}
export function getMostFrequentWeather(arr) {
const hashmap = arr.reduce((acc, val) => {
acc[val] = (acc[val] || 0) + 1;
return acc;
}, {});
return Object.keys(hashmap).reduce((a, b) =>
hashmap[a] > hashmap[b] ? a : b
);
}
export const descriptionToIconName = (desc, descriptions_list) => {
let iconName = descriptions_list.find((item) => item.description === desc);
return iconName.icon || 'unknown';
};
export const getWeekForecastWeather = (response, descriptions_list) => {
let foreacast_data = [];
let descriptions_data = [];
if (!response || Object.keys(response).length === 0 || response.cod === '404')
return [];
else
response?.list.slice().map((item, idx) => {
descriptions_data.push({
description: item.weather[0].description,
date: item.dt_txt.substring(0, 10),
});
foreacast_data.push({
date: item.dt_txt.substring(0, 10),
temp: item.main.temp,
humidity: item.main.humidity,
wind: item.wind.speed,
clouds: item.clouds.all,
});
return { idx, item };
});
const groupByDate = groupBy('date');
let grouped_forecast_data = groupByDate(foreacast_data);
let grouped_forecast_descriptions = groupByDate(descriptions_data);
const description_keys = Object.keys(grouped_forecast_descriptions);
let dayDescList = [];
description_keys.forEach((key) => {
let singleDayDescriptions = grouped_forecast_descriptions[key].map(
(item) => item.description
);
let mostFrequentDescription = getMostFrequentWeather(singleDayDescriptions);
dayDescList.push(mostFrequentDescription);
});
const forecast_keys = Object.keys(grouped_forecast_data);
let dayAvgsList = [];
forecast_keys.forEach((key, idx) => {
let dayTempsList = [];
let dayHumidityList = [];
let dayWindList = [];
let dayCloudsList = [];
for (let i = 0; i < grouped_forecast_data[key].length; i++) {
dayTempsList.push(grouped_forecast_data[key][i].temp);
dayHumidityList.push(grouped_forecast_data[key][i].humidity);
dayWindList.push(grouped_forecast_data[key][i].wind);
dayCloudsList.push(grouped_forecast_data[key][i].clouds);
}
dayAvgsList.push({
date: key,
temp: getAverage(dayTempsList),
humidity: getAverage(dayHumidityList),
wind: getAverage(dayWindList, false),
clouds: getAverage(dayCloudsList),
description: dayDescList[idx],
icon: descriptionToIconName(dayDescList[idx], descriptions_list),
});
});
return dayAvgsList;
};
export const getTodayForecastWeather = (
response,
current_date,
current_datetime
) => {
let all_today_forecasts = [];
if (!response || Object.keys(response).length === 0 || response.cod === '404')
return [];
else
response?.list.slice().map((item) => {
if (item.dt_txt.startsWith(current_date.substring(0, 10))) {
if (item.dt > current_datetime) {
all_today_forecasts.push({
time: item.dt_txt.split(' ')[1].substring(0, 5),
icon: item.weather[0].icon,
temperature: Math.round(item.main.temp) + ' °C',
});
}
}
return all_today_forecasts;
});
if (all_today_forecasts.length < 7) {
return [...all_today_forecasts];
} else {
return all_today_forecasts.slice(-6);
}
};
DateConstants.js
export const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
export const DAYS = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
];
export const ALL_DESCRIPTIONS = [
{ icon: '01d.png', description: 'clear sky' },
{ icon: '02d.png', description: 'few clouds' },
{ icon: '03d.png', description: 'scattered clouds' },
{ icon: '04d.png', description: 'broken clouds' },
{ icon: '04d.png', description: 'overcast clouds' },
{ icon: '09d.png', description: 'shower rain' },
{ icon: '09d.png', description: 'light intensity drizzle' },
{ icon: '09d.png', description: 'drizzle' },
{ icon: '09d.png', description: 'heavy intensity drizzle' },
{ icon: '09d.png', description: 'light intensity drizzle rain' },
{ icon: '09d.png', description: 'drizzle rain' },
{ icon: '09d.png', description: 'heavy intensity drizzle rain' },
{ icon: '09d.png', description: 'shower rain and drizzle' },
{ icon: '09d.png', description: 'heavy shower rain and drizzle' },
{ icon: '09d.png', description: 'shower drizzle' },
{ icon: '09d.png', description: 'light intensity shower rain' },
{ icon: '09d.png', description: 'shower rain' },
{ icon: '09d.png', description: 'heavy intensity shower rain' },
{ icon: '09d.png', description: 'ragged shower rain' },
{ icon: '10d.png', description: 'rain' },
{ icon: '10d.png', description: 'light rain' },
{ icon: '10d.png', description: 'moderate rain' },
{ icon: '10d.png', description: 'heavy intensity rain' },
{ icon: '10d.png', description: 'very heavy rain' },
{ icon: '10d.png', description: 'extreme rain' },
{ icon: '11d.png', description: 'thunderstorm' },
{ icon: '11d.png', description: 'thunderstorm with light rain' },
{ icon: '11d.png', description: 'thunderstorm with rain' },
{ icon: '11d.png', description: 'thunderstorm with heavy rain' },
{ icon: '11d.png', description: 'light thunderstorm' },
{ icon: '11d.png', description: 'heavy thunderstorm' },
{ icon: '11d.png', description: 'ragged thunderstorm' },
{ icon: '11d.png', description: 'thunderstorm with light drizzle' },
{ icon: '11d.png', description: 'thunderstorm with drizzle' },
{ icon: '11d.png', description: 'thunderstorm with heavy drizzle' },
{ icon: '13d.png', description: 'snow' },
{ icon: '13d.png', description: 'freezing rain' },
{ icon: '13d.png', description: 'light snow' },
{ icon: '13d.png', description: 'Snow' },
{ icon: '13d.png', description: 'Heavy snow' },
{ icon: '13d.png', description: 'Sleet' },
{ icon: '13d.png', description: 'Light shower sleet' },
{ icon: '13d.png', description: 'Light rain and snow' },
{ icon: '13d.png', description: 'Rain and snow' },
{ icon: '13d.png', description: 'Light shower snow' },
{ icon: '13d.png', description: 'Shower snow' },
{ icon: '13d.png', description: 'Heavy shower snow' },
{ icon: '50d.png', description: 'mist' },
{ icon: '50d.png', description: 'Smoke' },
{ icon: '50d.png', description: 'Haze' },
{ icon: '50d.png', description: 'sand/ dust whirls' },
{ icon: '50d.png', description: 'fog' },
{ icon: '50d.png', description: 'sand' },
{ icon: '50d.png', description: 'dust' },
{ icon: '50d.png', description: 'volcanic ash' },
{ icon: '50d.png', description: 'squalls' },
{ icon: '50d.png', description: 'tornado' },
];
DateTimeUtils.js
import { MONTHS, DAYS } from './DateConstants';
const date = new Date();
export function getWeekDays() {
const dayInAWeek = new Date().getDay();
const days = DAYS.slice(dayInAWeek, DAYS.length).concat(
DAYS.slice(0, dayInAWeek)
);
return days;
}
export function getDayMonthFromDate() {
const month = MONTHS[date.getMonth()].slice(0, 3);
const day = date.getUTCDate();
return day + ' ' + month;
}
export function transformDateFormat() {
const month = date.toLocaleString('en-US', { month: '2-digit' });
const day = date.toLocaleString('en-US', { day: '2-digit' });
const year = date.getFullYear();
const time = date.toLocaleString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
});
const newFormatDate = year.toString().concat('-', month, '-', day, ' ', time);
return newFormatDate;
}
export function getUTCDatetime() {
const utcTime = date.toLocaleString('en-US', {
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
timeZone: 'UTC',
});
const isoDateString = new Date().toISOString();
const utcDate = isoDateString.split('T')[0].concat(' ', utcTime);
return utcDate;
}
export function getUTCTime() {
const utcTime = date.toLocaleString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
timeZone: 'UTC',
});
return utcTime;
}
IconUtils.js
function importAll(r) {
let images = {};
r.keys().forEach((item, index) => {
images[item.replace('./', '')] = r(item);
});
return images;
}
export function weatherIcon(imageName) {
const allWeatherIcons = importAll(
require.context('../assets/icons', false, /\.(png)$/)
);
const iconsKeys = Object.keys(allWeatherIcons);
const iconsValues = Object.values(allWeatherIcons);
const iconIndex = iconsKeys.indexOf(imageName);
return iconsValues[iconIndex];
}
App.js
import React, { useState } from 'react';
import { Box, Container, Grid, Link, SvgIcon, Typography } from '@mui/material';
import Search from './components/Search/Search';
import WeeklyForecast from './components/WeeklyForecast/WeeklyForecast';
import TodayWeather from './components/TodayWeather/TodayWeather';
import { fetchWeatherData } from './api/OpenWeatherService';
import { transformDateFormat } from './utilities/DatetimeUtils';
import UTCDatetime from './components/Reusable/UTCDatetime';
import LoadingBox from './components/Reusable/LoadingBox';
import { ReactComponent as SplashIcon } from './assets/splash-icon.svg';
import Logo from './assets/logo.png';
import ErrorBox from './components/Reusable/ErrorBox';
import { ALL_DESCRIPTIONS } from './utilities/DateConstants';
import GitHubIcon from '@mui/icons-material/GitHub';
import {
getTodayForecastWeather,
getWeekForecastWeather,
} from './utilities/DataUtils';
function App() {
const [todayWeather, setTodayWeather] = useState(null);
const [todayForecast, setTodayForecast] = useState([]);
const [weekForecast, setWeekForecast] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const searchChangeHandler = async (enteredData) => {
const [latitude, longitude] = enteredData.value.split(' ');
setIsLoading(true);
const currentDate = transformDateFormat();
const date = new Date();
let dt_now = Math.floor(date.getTime() / 1000);
try {
const [todayWeatherResponse, weekForecastResponse] =
await fetchWeatherData(latitude, longitude);
const all_today_forecasts_list = getTodayForecastWeather(
weekForecastResponse,
currentDate,
dt_now
);
const all_week_forecasts_list = getWeekForecastWeather(
weekForecastResponse,
ALL_DESCRIPTIONS
);
setTodayForecast([...all_today_forecasts_list]);
setTodayWeather({ city: enteredData.label, ...todayWeatherResponse });
setWeekForecast({
city: enteredData.label,
list: all_week_forecasts_list,
});
} catch (error) {
setError(true);
}
setIsLoading(false);
};
let appContent = (
<Box
xs={12}
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
sx={{
width: '100%',
minHeight: '500px',
}}
>
<SvgIcon
component={SplashIcon}
inheritViewBox
sx={{ fontSize: { xs: '100px', sm: '120px', md: '140px' } }}
/>
<Typography
variant="h4"
component="h4"
sx={{
fontSize: { xs: '12px', sm: '14px' },
color: 'rgba(255,255,255, .85)',
fontFamily: 'Poppins',
textAlign: 'center',
margin: '2rem 0',
maxWidth: '80%',
lineHeight: '22px',
}}
>
Explore current weather data and 6-day forecast of more than 200,000
cities!
</Typography>
</Box>
);
if (todayWeather && todayForecast && weekForecast) {
appContent = (
<React.Fragment>
<Grid item xs={12} md={todayWeather ? 6 : 12}>
<Grid item xs={12}>
<TodayWeather data={todayWeather} forecastList={todayForecast} />
</Grid>
</Grid>
<Grid item xs={12} md={6}>
<WeeklyForecast data={weekForecast} />
</Grid>
</React.Fragment>
);
}
if (error) {
appContent = (
<ErrorBox
margin="3rem auto"
flex="inherit"
errorMessage="Something went wrong"
/>
);
}
if (isLoading) {
appContent = (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
minHeight: '500px',
}}
>
<LoadingBox value="1">
<Typography
variant="h3"
component="h3"
sx={{
fontSize: { xs: '10px', sm: '12px' },
color: 'rgba(255, 255, 255, .8)',
lineHeight: 1,
fontFamily: 'Poppins',
}}
>
Loading...
</Typography>
</LoadingBox>
</Box>
);
}
return (
<Container
sx={{
maxWidth: { xs: '95%', sm: '80%', md: '1100px' },
width: '100%',
height: '100%',
margin: '0 auto',
padding: '1rem 0 3rem',
marginBottom: '1rem',
borderRadius: {
xs: 'none',
sm: '0 0 1rem 1rem',
},
boxShadow: {
xs: 'none',
sm: 'rgba(0,0,0, 0.5) 0px 10px 15px -3px, rgba(0,0,0, 0.5) 0px 4px 6px -2px',
},
}}
>
<Grid container columnSpacing={2}>
<Grid item xs={12}>
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
sx={{
width: '100%',
marginBottom: '1rem',
}}
>
<Box
component="img"
sx={{
height: { xs: '16px', sm: '22px', md: '26px' },
width: 'auto',
}}
alt="logo"
src={Logo}
/>
<UTCDatetime />
<Link
href="https://github.com/Amin-Awinti"
target="_blank"
underline="none"
sx={{ display: 'flex' }}
>
<GitHubIcon
sx={{
fontSize: { xs: '20px', sm: '22px', md: '26px' },
color: 'white',
'&:hover': { color: '#2d95bd' },
}}
/>
</Link>
</Box>
<Search onSearchChange={searchChangeHandler} />
</Grid>
{appContent}
</Grid>
</Container>
);
}
export default App;
index.css
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&family=Roboto+Condensed:wght@300;400;700&display=swap');
body {
margin: 0;
font-family: 'Poppins', Arial, sans-serif !important;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
min-height: 100vh;
background: linear-gradient(-35deg, #000428 0%, #004e92);
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Final Output of Weather App

Create Student Profile Page Using HTML and CSS
Conclusion
Congratulations, You have completed your Mini Weather App ReactJs Project and further you can enhance this as per your preferences. You can deploy it on the Github, Vercel, or Netfliy platforms and make it live for your friends. Add this to your resume after adding or enhancing more functionality like City Details, Description of Weather, Sound Effects, Intractive UI, and many more. You can add further functionalities to your Weather App.
I hope you liked this Tutorial and must have learned Something new. If you have any questions regarding this feel free to drop your comments below and contact our team on Instagram @Codewithrandom.
HAPPY CODING!!!

