I made a weather app in react and redux for an interview. The response was that I didn't use redux properly, and that it has unnecessary props. I'm not sure what they meant.
The app is on github https://github.com/s-e1/weather-app
There are 2 pages in the App,
- Home contains SearchBar and HomeMain, HomeMain contains an array of ForecastCard.
- Favorites contains an array of CityCard.
reducer.js
const initialState = { cityName: "Tel Aviv", cityKey: 215854, searchResults: [], weatherResults: {} };
const reducer =
(state = initialState, action) => {
switch (action.type) {
case 'SET NAME':
return { ...state, cityName: action.payload };
case 'SET KEY':
return { ...state, cityKey: action.payload };
case 'SET SEARCH':
return { ...state, searchResults: action.payload };
case 'SET WEATHER':
return { ...state, weatherResults: action.payload };
default:
return state;
}
}
export default reducer;
utils.js - for sending requests to the server
import axios from "axios";
const server = "https://weather-app-serv.herokuapp.com/" || "http://localhost:8000/";
export const getSearchRequest = async (cityName) => {
if (!cityName) return [];
let reply = await axios.get(server + "search?name=" + cityName);
return reply.data;
}
export const getWeatherRequest = async (key) => {
const url = `${server}weather?key=${key}`;
let reply = await axios.get(url);
return reply.data;
}
export const getFavoritesRequest = async (key) => {
const url = `${server}favorites?key=${key}`;
let reply = await axios.get(url);
return reply.data;
}
app.js
import { useEffect } from "react";
import { Switch, Route } from "react-router-dom";
import { useDispatch, useSelector } from 'react-redux';
import Navbar from "./components/Navbar";
import Home from "./components/Home";
import Favorites from "./components/Favorites";
import { getSearchRequest, getWeatherRequest } from "./utils";
import "./App.css";
function App() {
const dispatch = useDispatch();
const cityName = useSelector(state => state.cityName);
const cityKey = useSelector(state => state.cityKey);
useEffect(() => {
weatherCB(cityName, cityKey);
}, [])
const searchCB = (name) => {
getSearchRequest(name)
.then(data => {
dispatch({ type: "SET SEARCH", payload: data });
})
}
const weatherCB = (name, key) => {
dispatch({ type: "SET NAME", payload: name });
dispatch({ type: "SET KEY", payload: key });
getWeatherRequest(key)
.then(data => {
dispatch({ type: "SET WEATHER", payload: data });
})
}
return (
<div className="appContainer">
<Navbar />
<Switch>
<Route exact path="/"><Home searchCB={searchCB} weatherCB={weatherCB} /></Route>
<Route path="/favorites"><Favorites weatherCB={weatherCB} /></Route>
</Switch>
<footer>
<p>Data provided by <a href="https://developer.accuweather.com/">Accuweather</a></p>
</footer>
</div>
);
}
export default App;
Home.js - contains SearchBar and HomeMain
import SearchBar from "./SearchBar";
import HomeMain from "./HomeMain";
import ErrorBoundary from "../ErrorBoundary";
function Home({ searchCB, weatherCB }) {
return (
<div>
<ErrorBoundary>
<SearchBar searchCB={searchCB} weatherCB={weatherCB} />
</ErrorBoundary>
<ErrorBoundary>
<HomeMain />
</ErrorBoundary>
</div>
);
}
export default Home;
SearchBar.js - a child of Home component
import { useState } from "react";
import { useSelector } from 'react-redux';
import "./SearchBar.css";
function SearchBar({ searchCB, weatherCB }) {
const searchResults = useSelector(state => state.searchResults);
const [text, setText] = useState("");
const search = (e) => {
setText(e.target.value);
searchCB(e.target.value);
}
const sendWeather = (name, key) => {
weatherCB(name, key);
setText("");
searchCB("");
}
return (
<div>
<div className="searchBar">Search:
<input onChange={search} value={text} />
</div>
{searchResults.length ?
<ul className="searchResults">
{searchResults.map((e, i) => {
return <li className="searchItem" key={i} onClick={() => sendWeather(e.cityName, e.key)}>
{e.cityName}, {e.countryName}
</li>
})}
</ul>
: null}
</div>
);
}
export default SearchBar;
HomeMain.js - a child of Home component
import { useEffect, useState } from "react";
import { useSelector } from 'react-redux';
import ForecastCard from "./ForecastCard";
import "./HomeMain.css";
function HomeMain() {
const cityName = useSelector(state => state.cityName);
const cityKey = useSelector(state => state.cityKey);
const weatherResults = useSelector(state => state.weatherResults);
const [currentWeather, setCurrentWeather] = useState({});
const [forecast, setForecast] = useState([]);
const [imgUrl, setImgUrl] = useState("");
const [isFavorite, setIsFavorite] = useState(false);
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const today = new Date().getDay();
const iconSite = "https://developer.accuweather.com/sites/default/files/";
useEffect(() => {
if (weatherResults.currentWeather || weatherResults.forecast) {
setCurrentWeather(weatherResults.currentWeather);
setForecast(weatherResults.forecast);
const url = iconSite + weatherResults.currentWeather.icon.padStart(2, '0') + "-s.png"
setImgUrl(url);
setIsFavorite(checkIfFavorite());
}
}, [weatherResults.currentWeather, weatherResults.forecast])
const checkIfFavorite = () => {
if (!localStorage.weatherApp) return false;
const arr = JSON.parse(localStorage["weatherApp"]);
const found = arr.some(e => e.cityName === cityName);
return found;
}
const addFavorite = () => {
setIsFavorite(true);
let arr;
if (!localStorage.weatherApp) {
arr = [];
} else {
arr = JSON.parse(localStorage["weatherApp"]);
const found = arr.some(e => e.cityName === cityName);
if (found) return;
}
arr.push({ cityKey, cityName });
localStorage["weatherApp"] = JSON.stringify(arr);
}
const removeFavorite = () => {
setIsFavorite(false);
const arr = JSON.parse(localStorage["weatherApp"]);
const filteredArr = arr.filter(e => e.cityName !== cityName);
localStorage["weatherApp"] = JSON.stringify(filteredArr);
}
return (
<div className="homeMain">
{currentWeather.text ?
<div >
{isFavorite ?
<button onClick={removeFavorite}>Remove From Favorites</button> :
<button onClick={addFavorite}>Add To Favorites</button>
}
<div className="currentWeather">
<h4>{cityName}</h4>
<img src={imgUrl} alt="icon" />
<div>{currentWeather.temperature + '\u00B0'}c</div>
<div>{currentWeather.text}</div>
</div>
</div>
: null}
{forecast.length ?
<div className="forecast">
{forecast.map((e, i) => {
return <ForecastCard key={i} high={e.high} low={e.low} day={daysOfWeek[(today + i) % 7]}> </ForecastCard>
})}
</div>
: null}
</div>
);
}
export default HomeMain;
ForecastCard.js - a child of HomeMain
import "./ForecastCard.css";
function ForecastCard({ day, high, low }) {
return (
<div className="forecastCard">
<strong>{day}</strong>
<div>H: {high + '\u00B0'}c </div>
<div>L: {low + '\u00B0'}c</div>
</div>
);
}
export default ForecastCard;
The 2nd page in the app for the Favorite cities weather, a child of App.
import { useEffect, useState } from "react";
import ErrorBoundary from "../ErrorBoundary";
import CityCard from "./CityCard";
import "./Favorites.css";
function Favorites({ weatherCB }) {
const [cityArray, setCityArray] = useState([]);
useEffect(() => {
if (!localStorage.weatherApp) return;
setCityArray(JSON.parse(localStorage["weatherApp"]));
}, [])
return (
<div>
<ErrorBoundary>
<div className="favorites">
{cityArray.length ? cityArray.map((e, i) => {
return <CityCard key={i} data={e} weatherCB={weatherCB} />
}) : null}
</div>
</ErrorBoundary>
</div>
);
}
export default Favorites;
CityCard.js is a child of Favorites.
import { useEffect, useState } from "react";
import { useHistory } from "react-router";
import { getFavoritesRequest } from "../utils";
import "./CityCard.css";
function CityCard({ data, weatherCB }) {
const [resData, setResData] = useState({});
let history = useHistory();
useEffect(() => {
getFavoritesRequest(data.cityKey)
.then(res => setResData(res));
}, [data])
const goToDetails = () => {
history.push("/");
weatherCB(data.cityName, data.cityKey);
}
return (
<div onClick={goToDetails} className="cityCard">
{resData.text ?
<div>
<h1>{data.cityName}</h1>
<div>{resData.temperature + '\u00B0'}c</div>
<div>{resData.text}</div>
</div>
: null}
</div>
);
}
export default CityCard;
Any suggestions about improving the code are welcome.