In this lesson we will be adding weather information to our react-blessed
Terminal Dashboard using the weather-js
node module. This module uses an error-first callback style API, but we'll convert it to a promise with util.promisify
so that we can use it via async
/await
.
Elijah Manor: [0:00] Here we have the beginnings of a Terminal Dashboard app written using react-blessed. If we come to the terminal and run npm start, you can see what we're dealing with. We have the current date and time that changes the font every half a second.
[0:14] The goal for this lesson is to augment what we already have and add some weather information to our terminal app. To grab the weather, we'll use the weather-js Node module from npm. The reason I picked this module is because it doesn't require a special token to use it. The information comes from the weather service of msn.com.
[0:36] We'll come back to the terminal and install the module with npm install weather-js. Before we integrate the library into our app, let's create a play.js file to experiment with the API. Then we'll come into our IDE and open up the play.js file.
[0:55] Let's start by creating a new weather variable and requiring the weather-js library. Then we'll call the find method off of weather and pass search of Nashville, TN, with a degreeType of F.
[1:12] This API takes a callback function that accepts an error and result. If there is an error we'll console.error the message, otherwise, we'll console.log the stringify version of the result and do a little formatting to make it read easier.
[1:30] If we come to the terminal and type node play, we'll get back a bunch of weather information about the location we search for, which was Nashville, TN. In our case, I don't really want to use the callback style API that's provided. I'd rather it be a promise so that I could also use it with async/await.
[1:49] Thankfully, in Node, there is a util.promisify method that will convert an error-first callback into a promise. To convert our weather API from a callback to a promise, we'll pull in util by requiring it, then create a findWeather variable and assign it to util.promisify and pass weather.find.
[2:14] Then we could change weather.find with findWeather, passing the same options. Now, call .then off of it for a successful result, and logging the stringify results. Then .catch if there's an error and console the error.
[2:36] Now, we should be able to come back to the terminal again and node play to see the same results as we had before. Yes, it still works.
Elijah Manor: [2:46] Before we introduce the weather into our Today component, let's do a bit of cleanup. We don't need the font changing every half second, so let's introduce some new state using React.useState of now and setNow and initialize it to new Date(), and then remove the const now variable.
[3:09] Then, in our useInterval, we'll swap out the setFontIndex with setNow and pass the current date for every 60,000 milliseconds, which is every one minute. If we go to the terminal and run our app, it no longer changes the time font twice a second, but stays the same and will update every minute.
[3:31] Let's focus on adding the weather. We'll import weather from "weather-js," and also import util from "util." Then, we'll create a new findWeather variable and assign it to util.promisify(weather.find). That will convert the callback style to a promise, so that we can use it with async/await.
[3:55] Let's quickly switch over to the dashboard and change the updateInterval passed into the Today component. We don't want to update the weather every 500 milliseconds, so let's change it to 900,000 milliseconds, which is 15 minutes.
[4:11] In our Today component, we'll also update the updateInterval to be 900,000 as a default value, and provide props for search with a default of Nashville, TN and a degreeType with a default of F.
[4:30] We'll add some new state, weather and setWeather by using a React.useState and initialize it to a status of loading, error of null, and data of null. Here we'll introduce a React.useEffect that will fetchWeather, and in our depths we'll include fetchWeather, which we haven't defined yet, so let's do that now.
[4:55] Our fetchWeather will be wrapped in React.useCallback, because we don't want to keep getting a new instance of it. It'll be an async function, and its dependency array will include search and degreeType.
[5:07] Before we fetch the weather, we'll update the weather state, so we know the status is currently loading, and we'll clear out the error and data. Then, we'll declare the data, and in a try, we'll await the results from the findWeather function, passing our search and degreeType.
[5:25] If that resolves successfully, then we'll update the weather state with status of complete and pass the data that was returned. If there was an exception, we'll catch that and update the weather state to indicate that there was an error and capture the problem.
[5:42] Although we aren't showing anything yet, let's go to the terminal and run our code. Unfortunately, we have a problem. If we scroll up a bit, we'll see a somewhat confusing error, ReferenceError, regeneratorRuntime is not defined.
[5:56] Either you've seen this before or after googling for a while you'll find out the problem is that we are in need of a special Babel plugin for async/await to work in our React app. The Babel plugin that we need is plugin-transform-runtime. We'll copy that, come back to our terminal, and npm install the dependency.
[6:17] To include the plugin in our app, we'll come over to our index.js file, where we kick off the app. Right after our presets, we'll add a new plugin section and paste in our plugin. If we come back and try our app again, we don't get an error, although we still aren't showing anything yet, so let's go back and do that.
[6:42] Let's scroll down to the return and now include the weather. After time, we'll check the weather.status. If it's loading, we'll show "Loading...", if there's an error, we'll show Error and show the actual problem. Otherwise, we'll call formatWeather, which we haven't written yet, and pass weather.data.
[7:06] Let's go define that now. Near the top of the file, we'll create a new formatWeather function that will accept an array. Here we'll destructure the first item and call it results. From the results, we'll destructure location, current, and forecast. Then pull out degreeType from the location and create a temperature from the current.temperature and mark it with the degreeType.
[7:34] We'll set the conditions to be current.skytext. The second forecast is the current one. We'll grab the low temperature from that and append the degreeType. We'll also do the same for the high temperature appending degreeType. Then, we'll return the temperature and the conditions and show the low value up to the high value.
[8:04] We should be able to come to our terminal again and run our app and briefly see a loading indicator while our app goes and gets the weather information, and then swaps it out with a nicely formatted weather summary.
[8:16] At this point, the weather information will only be updated once, or if the search or degreeType dependencies are changed. Let's go ahead and make it update every so often. We'll add another useInterval hook that updates every updateInterval. Inside we'll call fetchWeather().
[8:36] Instead of waiting 15 minutes, let's update the dashboard updateInterval to be 5,000 milliseconds or five seconds. Then we'll rerun our app. Now, after every five seconds, the weather information will be updated.
Hmm, clicking the "Available on GitHub" button on the Code tab doesn't navigate to the appropriate repo for you?
The "Available on GitHub" button works fine, I can't download the video
Cool lesson... but the download link doesn't work. I get an access denied error from CloudFront