Project overview:
Project shows: Uses React class components instead of functional components.
– It uses React componentDidMount() which is an earlier version of useEffect() with [].
– It uses React componentDidUpdate() which is an earlier version of useEffect() using [location]
– Parent class component ‘class App extends React.Component {…}’ as do child components.
– App.js contains all the components and uses ‘export default App’ where index.js uses ‘import App from “./App”‘ before it calls ‘root.render(…)’.
Code:
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(
<React.StrictMode>
<App />
</React.StrictMode>
);
import React from “react”;
function getWeatherIcon(wmoCode) {
const icons = new Map([
[[0], “☀️”],
[[1], “🌤”],
[[2], “⛅️”],
[[3], “☁️”],
[[45, 48], “🌫”],
[[51, 56, 61, 66, 80], “🌦”],
[[53, 55, 63, 65, 57, 67, 81, 82], “🌧”],
[[71, 73, 75, 77, 85, 86], “🌨”],
[[95], “🌩”],
[[96, 99], “⛈”],
]);
const arr = […icons.keys()].find((key) => key.includes(wmoCode));
if (!arr) return “NOT FOUND”;
return icons.get(arr);
}
function convertToFlag(countryCode) {
const codePoints = countryCode
.toUpperCase()
.split(“”)
.map((char) => 127397 + char.charCodeAt());
return String.fromCodePoint(…codePoints);
}
function formatDay(dateStr) {
return new Intl.DateTimeFormat(“en”, {
weekday: “short”,
}).format(new Date(dateStr));
}
class App extends React.Component {
state = {
location: “”,
isLoading: false,
displayLocation: “”,
weather: {},
};
/*constructor(props) {
super(props);
//this.fetchWeather = this.fetchWeather.bind(this);
}*/
//async fetchWeather() {
fetchWeather = async () => {
if (this.state.location.length < 2) return this.setState({ weather: {} });
try {
this.setState({ isLoading: true });
// 1) Getting location (geocoding)
const geoRes = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${this.state.location}`
);
const geoData = await geoRes.json();
console.log(geoData);
if (!geoData.results) throw new Error(“Location not found”);
const { latitude, longitude, timezone, name, country_code } =
geoData.results.at(0);
this.setState({
displayLocation: `${name} ${convertToFlag(country_code)}`,
});
// 2) Getting actual weather
const weatherRes = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&timezone=${timezone}&daily=weathercode,temperature_2m_max,temperature_2m_min`
);
const weatherData = await weatherRes.json();
this.setState({ weather: weatherData.daily });
} catch (err) {
console.err(err);
} finally {
this.setState({ isLoading: false });
}
};
setLocation = (e) => this.setState({ location: e.target.value });
// called immediately after rendering same as useEffect with []
componentDidMount() {
//this.fetchWeather();
this.setState({ location: localStorage.getItem(“location”) || “” });
}
// React gives it access to the previous state and the previous props
// same as useEffect with [location]
componentDidUpdate(prevProps, prevState) {
if (this.state.location !== prevState.location) {
this.fetchWeather();
localStorage.setItem(“location”, this.state.location);
}
}
render() {
return (
<div className=“app”>
<h1>Classy Weather</h1>
<div>
<Input
location={this.state.location}
onChangeLocation={this.setLocation}
/>
</div>
{this.state.isLoading && <p className=“loader”>Loading…</p>}
{this.state.weather.weathercode && (
<Weather
weather={this.state.weather}
location={this.state.displayLocation}
/>
)}
</div>
);
}
}
export default App;
class Input extends React.Component {
render() {
return (
<input
type=“text”
placeholder=“Search for location…”
value={this.props.location}
// now using child to parent communication and it is just as
// import in class based components as it is in function
// based components
onChange={this.props.onChangeLocation}
/>
);
}
}
// when you don’t need state and you don’t need to explicitely bind the
// this keyword you don’t need a constructor method!
class Weather extends React.Component {
// when there is no string there is no weather
componentWillUnmount() {
console.log(“Weather is unmounting”);
}
render() {
const {
temperature_2m_max: max,
temperature_2m_min: min,
time: dates,
weathercode: codes,
} = this.props.weather;
console.log(this.props);
return (
<div>
<h2>Weather {this.props.location}</h2>
<ul className=“weather”>
{dates.map((date, i) => (
<Day
date={date}
max={max.at(i)}
min={min.at(i)}
code={codes.at(i)}
key={date}
isToday={i === 0}
/>
))}
</ul>
</div>
);
}
}
class Day extends React.Component {
render() {
const { date, max, min, code, isToday } = this.props;
return (
<li className=“day”>
<span>{getWeatherIcon(code)}</span>
<p>{isToday ? “Today” : formatDay(date)}</p>
<p>
{Math.floor(min)}° — <strong>{Math.ceil(max)}°</strong>
</p>
</li>
);
}
}
@import url(“https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700&display=swap”);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
}
body {
font-family: “Cinzel Decorative”, cursive;
font-size: 2rem;
height: 100vh;
color: #222;
background-color: #eabfb9;
display: flex;
align-items: center;
justify-content: center;
}
.app {
display: flex;
flex-direction: column;
align-items: center;
width: 112rem;
gap: 4rem;
padding: 6rem 0;
border: 2px solid #222;
outline: 2px solid #222;
outline-offset: 1.2rem;
}
h1 {
font-weight: 400;
font-size: 6rem;
letter-spacing: 2px;
word-spacing: 5px;
}
h2 {
text-align: center;
margin-bottom: 2.4rem;
}
input {
font-family: inherit;
color: inherit;
font-size: 2rem;
background-color: #f0d2ce;
padding: 1.6rem 3.2rem;
border: none;
width: 32rem;
}
input:focus {
outline: 2px solid #222;
}
.weather {
list-style: none;
display: flex;
gap: 2.4rem;
}
.day {
background-color: #f0d2ce;
padding: 1.6rem 0 2rem;
width: 12.8rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.2rem;
cursor: pointer;
}
.day span {
font-size: 5.2rem;
}
.day.important {
background-color: #ecc5c0;
outline: 2px solid #222;
}
.loader {
font-size: 2.4rem;
font-weight: 700;
}