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
ADVERTISEMENT
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:-
ADVERTISEMENT
DailyForecast.js
ADVERTISEMENT
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
ADVERTISEMENT
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
ADVERTISEMENT
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!!!