-
Notifications
You must be signed in to change notification settings - Fork 85
redux-weather-wla #77
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
base: master
Are you sure you want to change the base?
Changes from all commits
d210570
b90c1e5
9e1a863
83cc229
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import axios from "axios"; | ||
|
|
||
| export const FETCH_FORECAST = "FETCH_FORECAST"; | ||
|
|
||
| // stored API key in environment variable | ||
| const API_KEY = process.env.REACT_APP_WEATHER_API_KEY; | ||
| const urlForecast = 'https://api.openweathermap.org/data/2.5/forecast?q=' | ||
|
|
||
| // action to get forecast data from weather API | ||
| export function fetchForecast(query) { | ||
| const request = axios.get(`${urlForecast}${query.city}&appid=${API_KEY}&units=imperial`) | ||
|
|
||
| return { | ||
| type: FETCH_FORECAST, | ||
| payload: request | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines'; | ||
| import { useSelector } from 'react-redux'; | ||
| import _ from 'lodash'; | ||
|
|
||
|
|
||
| const ForecastList = () => { | ||
| // pulled state in to be able to render all forecast data | ||
| const forecasts = useSelector((state) => state); | ||
|
|
||
| // function to render forecast data using Sparklines to show the 5 day forecast of temperature, pressure, and humidity. Includes reference line to show 5 day average. | ||
| const renderForecasts = () => { | ||
| if (!_.isEmpty(forecasts.entries)) { | ||
| return forecasts.cityIds.map((id) => { | ||
| return ( | ||
| <tr key={id}> | ||
| <td>{forecasts.entries[id].city}</td> | ||
| <td> | ||
| <Sparklines data={forecasts.entries[id].temps} height={150}> | ||
| <SparklinesLine color='orange'/> | ||
| <SparklinesReferenceLine type='mean'/> | ||
| </Sparklines> | ||
|
|
||
| {/* function to display the 5 day average value. Is there an easier way to do this? Since ReferenceLine is also calculating average, can a value be pulled from that? */} | ||
| <div>{Math.round(forecasts.entries[id].temps.reduce((a, b) => a + b) / forecasts.entries[id].temps.length)} °F</div> | ||
| </td> | ||
| <td> | ||
| <Sparklines data={forecasts.entries[id].pressure} height={150}> | ||
| <SparklinesLine color='green'/> | ||
| <SparklinesReferenceLine type='mean'/> | ||
| </Sparklines> | ||
| <div>{Math.round(forecasts.entries[id].pressure.reduce((a, b) => a + b) / forecasts.entries[id].pressure.length)} hPa</div> | ||
| </td> | ||
| <td> | ||
| <Sparklines data={forecasts.entries[id].humidity} height={150}> | ||
| <SparklinesLine color='blue'/> | ||
| <SparklinesReferenceLine type='mean'/> | ||
| </Sparklines> | ||
| <div>{Math.round(forecasts.entries[id].humidity.reduce((a, b) => a + b) / forecasts.entries[id].humidity.length)} %</div> | ||
| </td> | ||
| </tr> | ||
| ) | ||
| }); | ||
| } else { | ||
| return <tr><td>*Enter a city above for a forecast*</td></tr> | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <table className='table text-center align-middle'> | ||
| <thead> | ||
| <tr> | ||
| <th scope='col' className='col-md-3'>City</th> | ||
| <th scope='col' className='col-md-3'>Temperature(°F)</th> | ||
| <th scope='col' className='col-md-3'>Pressure(hPa)</th> | ||
| <th scope='col' className='col-md-3'>Humidity(%)</th> | ||
| </tr> | ||
| </thead> | ||
|
|
||
| <tbody> | ||
| {renderForecasts()} | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
|
|
||
| ) | ||
| } | ||
|
|
||
| export default ForecastList; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| const header = (props) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like how you broke this out into components, nice work! |
||
| return ( | ||
| <div> | ||
| <div className='jumbotron text-center'> | ||
| <div className="container"> | ||
| <h1 className="jumbotron-heading">Redux Weather</h1> | ||
| </div> | ||
| </div> | ||
| <div className='container'> | ||
| {props.children} | ||
| </div> | ||
| </div> | ||
| ) | ||
| }; | ||
|
|
||
| export default header; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { useForm } from "react-hook-form"; | ||
| import * as Yup from "yup"; | ||
| import { yupResolver } from "@hookform/resolvers/yup"; | ||
| import { useDispatch } from "react-redux"; | ||
| import { fetchForecast } from "../actions"; | ||
|
|
||
| // not sure if necessary, but using yup to add form validation | ||
| const postSchema = Yup.object().shape({ | ||
| city: Yup.string().required() | ||
| }); | ||
|
|
||
| const SearchBar = () => { | ||
|
|
||
| // using react-hook-form for form validation | ||
| const { register, handleSubmit, errors, reset } = useForm({ | ||
| resolver: yupResolver(postSchema) | ||
| }); | ||
|
|
||
| const dispatch = useDispatch(); | ||
|
|
||
| //dispatches action when form is submitted | ||
| const handleFormSubmit = (query) => { | ||
| dispatch( | ||
| fetchForecast(query) | ||
| ) | ||
|
|
||
| // clears value from input once submitted | ||
| reset(); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing some semi-colons |
||
|
|
||
| return ( | ||
| <form onSubmit={handleSubmit(handleFormSubmit)} className='container row'> | ||
| <div className='form-group'> | ||
| <label className='form-label'>Get a 5-day forecast</label> | ||
| </div> | ||
| <div className='mb-3'> | ||
| <div className='input-group'> | ||
| <input className='form-control' type='text' placeholder='Please enter a city and click Search' name='city' ref={register}></input> | ||
| <button className='btn btn-primary' type='submit'>Search</button> | ||
| </div> | ||
| {errors.city?.message} | ||
| </div> | ||
| <hr /> | ||
| </form> | ||
| ) | ||
| } | ||
|
|
||
| export default SearchBar; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,10 @@ | ||
| body { | ||
| margin: 0; | ||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | ||
| 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | ||
| sans-serif; | ||
| -webkit-font-smoothing: antialiased; | ||
| -moz-osx-font-smoothing: grayscale; | ||
| .jumbotron { | ||
| padding: 30px 20px; | ||
| margin-bottom: 2rem; | ||
| background-color: #e9ecef; | ||
| border-radius: 0.3rem; | ||
| } | ||
|
|
||
| code { | ||
| font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', | ||
| monospace; | ||
| .forecast-data { | ||
| line-height: 200px; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,26 @@ | ||
| import 'bootstrap/dist/css/bootstrap.css'; | ||
| import React from 'react'; | ||
| import ReactDOM from 'react-dom'; | ||
| import './index.css'; | ||
| import App from './App'; | ||
| import reportWebVitals from './reportWebVitals'; | ||
| import { createStore, applyMiddleware } from 'redux'; | ||
| import promise from 'redux-promise'; | ||
| import { Provider } from 'react-redux'; | ||
|
|
||
| import Header from './components/header'; | ||
| import SearchBar from './components/search-bar'; | ||
| import ForecastList from './components/forecast-list'; | ||
| import forecastReducer from './reducers/reducer-forecast'; | ||
|
|
||
| const createStoreWithMiddleware = applyMiddleware(promise)(createStore); | ||
|
|
||
| ReactDOM.render( | ||
| <React.StrictMode> | ||
| <App /> | ||
| </React.StrictMode>, | ||
| <Provider store={createStoreWithMiddleware(forecastReducer)}> | ||
| <React.StrictMode> | ||
| <Header> | ||
| <SearchBar /> | ||
| <ForecastList /> | ||
| </Header> | ||
| </React.StrictMode> | ||
| </Provider>, | ||
| document.getElementById('root') | ||
| ); | ||
|
|
||
| // If you want to start measuring performance in your app, pass a function | ||
| // to log results (for example: reportWebVitals(console.log)) | ||
| // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | ||
| reportWebVitals(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { FETCH_FORECAST } from "../actions"; | ||
| import _ from 'lodash'; | ||
|
|
||
| const DEFAULT_STATE = { | ||
| entries: {}, | ||
| cityIds: [] | ||
| }; | ||
|
|
||
| // the entries parameter in state will be an object of objects that lists the cities by their id and contains data for city name, temp, pressure, and humidity. cityIds was added as an array so the id's could be mapped through and used to select the correct objects within entries when rendering forecasts in ./components/forecast-list.js | ||
| const forecastReducer = function (state = DEFAULT_STATE, action) { | ||
| switch (action.type) { | ||
| case FETCH_FORECAST: | ||
| return { | ||
| entries: { ...state.entries, [action.payload.data.city.id]: { | ||
| city: action.payload.data.city.name, | ||
|
|
||
| // creates an array of all the 5 day forecast data for temp, pressure, and humidity. The array will be used for sparklines | ||
| temps: action.payload.data.list.map(obj => obj.main.temp), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't recommend using generic terms like obj to name things. Be as descriptive as possible with naming. |
||
| pressure: action.payload.data.list.map(obj => obj.main.pressure), | ||
| humidity: action.payload.data.list.map(obj => obj.main.humidity) | ||
| } | ||
| }, | ||
| cityIds: _.union([...state.cityIds], [action.payload.data.city.id]) | ||
| } | ||
| default: | ||
| return state; | ||
| } | ||
| } | ||
|
|
||
| export default forecastReducer; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a lot of inconsistency in how you are writing functions (expression/declaration). It is good to stay consistent.