Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect to Weather API #28

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
58 changes: 55 additions & 3 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,71 @@
import "./App.scss";
import React, { useState } from "react";
import Header from "./components/Header/Header";
import Footer from "./components/Footer/Footer";

import WeatherIcon from "./components/Picture/WeatherIcon";
import CurrentWeather from "./components/CurrentWeather/CurrentWeather";
import FutureWeather from "./components/FutureForecast";


import Icon from './components/Icon/Icon';

//configs
const siteTitle = process.env.REACT_APP_SITE_TITLE ?? "CYF Weather";

function App() {
const [weatherData, setWeatherData] = useState([]);

function getNewWeather(city) {
fetch(
`https://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&appid=3b86046cce0de3be7cfa8369f4540b37`
)
.then((res) => res.json())
.then((data) => {
setWeatherData(data);
});
}

return (
<div className="App">
<Header title={siteTitle} />
<Header
title={siteTitle}
getNewWeather={getNewWeather}
weatherData={weatherData}
/>

<main className="c-site-main" tabIndex="0">

<section>
{/* <WeatherIcon weatherId={weatherData?.weather?.[0]?.id} /> */}
{weatherData?.list?.map((icon) => (
<WeatherIcon weatherId={icon.weather[0].id} />
))}

{weatherData?.list?.map((current) => (
<CurrentWeather
description={current.weather.description}
temp={current.main.temp.toFixed()}
humidity={current.main.humidity}
pressure={current.main.pressure}
/>
))}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's think about this @zkhing
<CurrentWeather /> is just one item of weather. The weather at a specific time.
Unlike FutureWeather which is a list of several items of weather.

So does it makes sense to .map() over the data here? When all we want is one item?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Lucy, it makes sense not to use .map( ) but I couldn't fetch current weather from API for somehow.

</section>

<section>
{weatherData?.list?.map((future) => (
<FutureWeather
time={future.dt_txt}
iconId={future.weather[0].id}
temp={future.main.temp.toFixed()}
/>
))}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we do want more than one item, however:

  1. We don't want all the data available. There are 40 items in the API data, but we only want to display 7 items (we know this because we are following the design. Always use the design to decide what your code needs to do).
  2. It would be best to pass the future weather data to the FutureWeather component, and the component itself should .map() over it and display each item. Rather than mapping here inside App.js.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I could mange to display 7 items. But positioning is incorrect and I am not sure whether I should use CSS grid instead of flexbox.
  2. I tried to pass the future weather data to the FutureWeather component as you suggested but again I couldn't fetch data from API in child component.

Copy link

@LucyMac LucyMac Mar 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. You can use grid but you should be able to achieve the layout with just flexbox. It's up to you. The flex container should be full width but have some padding on the sides, so it won't actually look completely full width which is what we want.
    The flex items should be placed in a row, centered and with space-between.
    Then within each item, you should use flexbox again, this time to place the time, icon and temperature in a column. Again centered.

  2. You do not need to call the API again! The whole point is to call it once in the parent, and then pass the array of 7 weather items to the FutureWeather component as a prop.
    So you should add a prop called for example 'weatherItems' to the FutureWeather component, with your array of 7 items as the value.
    Then inside FutureWeather you will receive props.weatherItems and .map() over it (since it is an array). And for each item in the map you can display your weather item with time, icon and temp.

Hope this makes sense! Give it a go and I'll have a look when you're ready.

</section>


<Icon name="clear"/>

</main>

<Footer />
</div>
);
Expand Down
59 changes: 59 additions & 0 deletions src/App.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,61 @@
@use "theme/utilities.scss";
@use "theme/global.scss";


.App{
background-color: #8ecae6;
}

.c-site-header{
padding: 20px;
}

.heading{
background-color: #219ebc;
padding: 10px;
}

.c-site-header__title{
color: #023047;
padding: 20px;
}

.city{
margin: 15px;
padding: 15px;
}

.search-btn{
background-color: #023047;
color: white;
margin-left: 15px;
padding: 15px;
}

.weather-img{
margin: auto;
width: 200px;
height: 200px;
object-fit: cover;
}

.description{
display: flex;
justify-content: center;
color: white;
}

.temp {
display: flex;
justify-content: center;
margin: 20px;
}

.box{
display: flex;
justify-content: center;
}

.sub-box{
padding: 30px;
}
27 changes: 27 additions & 0 deletions src/components/CurrentWeather/CurrentWeather.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'

function CurrentWeather({ description, temp, humidity, pressure }) {
return (
<>
<div className="description">
<h2>{description}</h2>
</div>

<div className="temp">
<h3>Temperature : {temp}°C</h3>
</div>

<div className="box">
<div className="sub-box">
<h4>Humidity : {humidity}%</h4>
</div>

<div className="sub-box">
<h4>Pressure : {pressure}</h4>
</div>
</div>
</>
);
}

export default CurrentWeather
16 changes: 16 additions & 0 deletions src/components/FutureForecast.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import WeatherIcon from "./Picture/WeatherIcon";


export default function FutureWeather({ time, iconId, temp }) {

const formattedTime = time.split(" ")[1].slice(0,5)

return (
<div>
<p>{formattedTime}</p>
<WeatherIcon weatherId={iconId} />
<p>{temp}</p>
</div>
);
}
30 changes: 23 additions & 7 deletions src/components/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import React from "react";
import './Header.scss'
import "./Header.scss";
// import WeatherIcon from "../Picture/WeatherIcon";
import React, { useState } from "react";

const Header = ({title}) =>
const Header = ({ title, getNewWeather, weatherData }) => {
const [city, setCity] = useState("");

return (
<header className="c-site-header">
<h1 className="c-site-header__title o-type__invisible">{title}</h1>
{/* look up component */}
<div className="heading">
<h1 className="c-site-header__title">{title}</h1>
<input
className="city"
type="text"
placeholder="Type in a city name"
value={city}
onChange={(event) => setCity(event.target.value)}
/>
<button className="search-btn" onClick={() => getNewWeather(city)}>
FIND WEATHER
</button>
</div>
</header>
);
};


export default Header;
export default Header;
57 changes: 57 additions & 0 deletions src/components/Picture/WeatherIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import storm from "../../img/weather-icons/storm.svg";
import drizzle from "../../img/weather-icons/drizzle.svg";
import rain from "../../img/weather-icons/rain.svg";
import snow from "../../img/weather-icons/snow.svg";
import fog from "../../img/weather-icons/fog.svg";
import clear from "../../img/weather-icons/clear.svg";
import partlyCloudy from "../../img/weather-icons/partlycloudy.svg";
import mostlyCloudy from "../../img/weather-icons/mostlycloudy.svg";
import unknown from "../../img/weather-icons/unknown.svg"

const images = {
storm: { src: storm, alt: "storm" },
drizzle: { src: drizzle, alt: "drizzle" },
rain: { src: rain, alt: "rain" },
snow: { src: snow, alt: "snow" },
fog: { src: fog, alt: "fog" },
clear: { src: clear, alt: "clear" },
partlyCloudy: { src: partlyCloudy, alt: "partly cloudy" },
mostlyCloudy: { src: mostlyCloudy, alt: "mostly cloudy" },
unknown: { src: unknown, alt: "unknown" },
};


function selectImage(weatherId){
if (weatherId < 300) {
return images.storm;
} else if (weatherId < 499) {
return images.drizzle;
} else if (weatherId < 599) {
return images.rain;
} else if (weatherId < 699) {
return images.snow;
} else if (weatherId < 799) {
return images.fog;
} else if (weatherId === 800) {
return images.clear;
} else if (weatherId === 801) {
return images.partlyCloudy;
} else if (weatherId < 805 && weatherId > 801) {
return images.mostlyCloudy;
}
return images.unknown;
}

const WeatherIcon = ({weatherId}) =>{

const image = selectImage(weatherId)

return (
<div className="weather-img">
<img src={image.src} alt={image.alt} />
</div>
);
}

export default WeatherIcon;
Loading