Get your team started on a custom learning journey today!
Our Boulder, CO-based learning experts are ready to help!
Get your team started on a custom learning journey today!
Our Boulder, CO-based learning experts are ready to help!
Follow us on LinkedIn for our latest data and tips!
Let’s face it, while it’s fun to write applications from scratch, a lot of software development work involves working with existing code. In this post, we are going to learn how to add a feature to an existing React/Redux app. Following on our previous articles in <list filtering with react/redux> and <component interactivity with react/redux>, we will add a feature to get a weather forecast estimate to help us figure out when is the best time to visit crater lake. In addition, since we are querying an API, we will be learning how to use async actions to retrieve and display the weather.
Here’s an outline of the process we will be following
Check out the final product on Heroku and get the code on Github.
Using the react-datepicker module we’ll create a new WeatherDatePicker component to show the date picker and the forecast results. When the user loads the page they will see “Select a date…”:
After a date is selected, the forecast will be displayed:
Taking a look at the render method in WeatherDatePicker.jsx:
render() { return ( <div> Travel Date: <DatePicker placeholderText="Select a date..." selected={this.props.selectedDate} dateFormat = "YYYY-MM-DD" onChange={(selectedDate) => { let newDate = this.checkDate(selectedDate) if (newDate) { this.props.fetchWeather(this.checkDate(selectedDate), this.props.currentLat, this.props.currentLong) } } }/> Forecast: {this.props.weatherSummary} </div> ); }
When the DatePicker is opened and a date is selected, firing the onChange event, we first check the date to make sure it is in the future:
checkDate(selectedDate) { if (selectedDate.isBefore(this.props.currentDate)) { alert("Please pick a date in the future.") return undefined } else { return selectedDate.format("YYYY-MM-DD") } }
The DatePicker module uses moment.js for the date object. We can use moment’s isBefore method to compare two moment objects, and display an alert if the user picks a date in the past. If the render method receives a valid result, it calls the delegate fetchWeather, passing along the selected date, to retrieve the weatherSummary.
Now we will add the WeatherDatePicker component and the DarkSky attribution graphic to CampFilterApp.jsx:
render() { return ( <div className="container"> <div><a href="https://darksky.net/poweredby/"><img src="https://darksky.net/dev/img/attribution/poweredby.png" style={{ width: 100 }}/></a></div> <Jumbotron> <h1>Crater Lake Camping</h1> </Jumbotron> <br></br> <CampFilterList {...this.props}/> <br></br> <WeatherDatePicker {...this.props}/> <br></br> <CampMapContainer {...this.props} /> </div> ) };
While we are working with components, let’s also update the Redux store to include the thunk middleware that we’ll be using for the next steps. We just need to add thunkMiddleware to our createStore call. In index.js:
import thunkMiddleware from 'redux-thunk' const store = createStore(reducer, applyMiddleware( thunkMiddleware ))
Now that we have a component to display the weather, lets look into the associated actions and state needed to do the work.
For the weather feature, the following fields are added to the state in index.js and set as props in CampFilterApp.jsx:
currentDate: today, weatherSummary: "", currentLat: 42.9456, currentLong: -122.2, selectedDate: undefined
The DarkSky API needs a position and date to retrieve the weather. Since our app is localized to Crater Lake we’ll use those coordinates to retrieve the weather. Don’t forget to convert these new state variables to props to send down to the WeatherDatePicker component in CampFilterApp.jsx:
function mapStateToProps(state) { return { ... weatherSummary: state.get('weatherSummary'), currentDate: state.get('currentDate'), currentLat: state.get('currentLat'), currentLong: state.get('currentLong'), selectedDate: state.get('selectedDate') }; }
To query the API and update the selectedDate and weatherSummary variables we will use thunk middleware and fetch as described in this article from the Redux docs.
To handle an async action, we will split the API call into request and receive actions. Our request action, REQ_WEATHER, will update the selectedDate to the value passed from the DatePicker and weatherSummary to “Loading…” to let the user know the weather info is loading. From action_creators.js:
export function reqWeather(weatherDate) { return { type: 'REQ_WEATHER', weatherDate } }
And the corresponding function in reducer.js:
function reqWeather(state, weatherDate) { return state.merge(Map({ 'weatherSummary': "Loading...", 'selectedDate': weatherDate })) }
For the receive action, RECV_WEATHER, we add a response field to our state to hold the JSON returned by the DarkSky API:
export function recvWeather(weatherDate, result) { return { type: 'RECV_WEATHER', weatherDate, response: result } }
The corresponding method in reducer.js for parsing the API response into the weatherSummary field:
function recvWeather(state, weatherDate, result) { let content = "" try { let tempMax = result.daily.data[0].temperatureMax let tempMin = result.daily.data[0].temperatureMin let summary = result.daily.data[0].summary content = summary + " High: " + Math.ceil(tempMax) + " Low: " + Math.ceil(tempMin) } catch (err) { console.log("couldnt get weather summary: " + err) } return state.merge(Map({ 'weatherSummary': content }))
Alright, at this point you may be wondering “This is great and all, but where’s the part where we actually query the API?” Excellent question! That’s next.
Now that we have our request and receive actions setup, lets combine them in the fetchWeather action, which if you recall, is what is getting called from the WeatherDatePicker onChange event:
export function fetchWeather(weatherDate, currentLat, currentLong) { let request_url = "https://crossorigin.me/https://api.darksky.net/forecast/8266ff95ef9bbfccf0ea24c325818f31/" let weather_str = weatherDate.format("YYYY-MM-DD") + "T00:00:00" request_url = request_url + currentLat + "," + currentLong + "," + weather_str return function (dispatch) { dispatch(reqWeather(weatherDate)) return fetch(request_url) .then(response => response.json()) .catch(error => { console.log("unable to get weather " + error) }) .then(respData => { dispatch(recvWeather(weatherDate, respData)) }) .catch(error => { console.log("unable to parse weather result " + error) }) } }
Stepping through this piece by piece – first we are constructing the URL for the DarkSky request. As explained in the Redux article, this is where the thunk middleware comes into play; this is what allows the fetchWeather action to return a function instead of an object.
The first thing the returned function from fetchWeather does is dispatch the reqWeather action to update the UI showing that we are requesting the weather. It then fetches the response from the DarkSky API, dispatching the recvWeather action if it receives a valid result.
Now, we have all the plumbing hooked up to retrieve the weather based on the date chosen in the date picker. Give it a try and download the code. If you want to make updates and deploy your own Heroku app make sure to link the create-react-app buildpack in Settings.
Some next steps to try:
Customized Technical Learning Solutions to Help Attract and Retain Talented Developers
Let DI help you design solutions to onboard, upskill or reskill your software development organization. Fully customized. 100% guaranteed.
DevelopIntelligence leads technical and software development learning programs for Fortune 500 companies. We provide learning solutions for hundreds of thousands of engineers for over 250 global brands.
“I appreciated the instructor’s technique of writing live code examples rather than using fixed slide decks to present the material.”
VMwareDevelopIntelligence has been in the technical/software development learning and training industry for nearly 20 years. We’ve provided learning solutions to more than 48,000 engineers, across 220 organizations worldwide.
Thank you for everyone who joined us this past year to hear about our proven methods of attracting and retaining tech talent.
© 2013 - 2022 DevelopIntelligence LLC - Privacy Policy
Let's review your current tech training programs and we'll help you baseline your success against some of our big industry partners. In this 30-minute meeting, we'll share our data/insights on what's working and what's not.
Training Journal sat down with our CEO for his thoughts on what’s working, and what’s not working.