This is the curriculum for the 2018 Foxtrot Web Developer Bootcamp.

01-Intro from LEARN on Vimeo

Overview

Today we're going to be connecting a React application with an existing API, pulling in data and displaying it to our users.

Concepts

  • Using API keys
  • JSON APIs
  • curl
  • Using Cached API calls
  • Mapping data from an API
  • Iterating over an array in a React Component
  • Using fetch to make API calls
  • Deploying to Heroku

02 create-react-app from LEARN on Vimeo

Create a new React application

In this app, we're going to use the familiar create-react-app to build an app we can start working with. We'll also add a Bootstrap package to allow us to treat Bootstrap elements as regular React components. Its a great way to work with both technologies, and they work really well together. Then, to give the project a bit of refinement, we'll go in search of a theme for Bootstrap.

Steps

  • create-react-app nasa-neo
  • yarn add react-bootstrap
  • Grab theme from Bootswatch
  • Move bootstrap.min.css file from theme into /public directory
  • Add <link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.min.css"> to /public/index.html

react-bootstrap

website

Bootstrap outside of React, at its core, is a set of CSS classes applied to elements, and this makes it very easy to work with when writing HTML. Consider the following that may be used for a typical left navigation website:

<div class='container'>
  <div class='row'>
    <div class='col-xs-4'>
      <!-- html for left nav here -->
    </div>
    <div class='col-xs-8'>
      <!-- html for main container here -->
    </div>
  </div>
</div>

That works great in the world of HTML, and gives us loads of control over the look and feel of our webpage. But, when using the React framework, we want to think about the page as a set of components. Divs, while technically a component in React, are pretty generic ones. Wouldn't it be nicer to write the following instead?

<Container>
  <Row>
    <Col xs={4}>
      {/* html for left nav here */}
    </Col>
    <Col xs={8}>
      {/* html for main container here */}
    </Col>
  </Row>
</Container>

That's what react-bootstrap is all about. Declarative, component syntax for Bootstrap within React.

Don't forget to import the components you use

A word of caution. A very common mistake is to forget to import the Bootstrap components you use in your JSX at the top of the page. You'll see in later videos that we add an import statement in components that use Bootstrap elements, and explicitly import each component. Something like this:

import {
  PageHeader,
  Table
} from 'react-bootstrap'

All Bootstrap components are imported from 'react-bootstrap', so we can list them all out out in one import statement.

Challenges

  • Use create-react-app to build a new application about any topic you choose
  • Use Yarn to add 'react-bootstrap' to your new React app
  • Add a Theme to your React app.
  • Import some Bootstrap components, and build a nice looking web page.

03 APIs from LEARN on Vimeo

Working with APIs

We're going to be fetching data from the NASA Near Earth Object API. This API gives us all the data we'll need to show users a list of near earth objects on the page.

Break the task down into smaller tasks

Working with API data is a complex task as a developer. We need perform several tasks in order to get the data from the API, and onto our web page. A great strategy is to break a big task like this down into smaller tasks. Consider the following steps:

1) Make a request to the API for the data
2) Handle the response from the API (whether success or failure)
3) Map the data from the API into a usable form
4) Use the mapped data to render our webpage

That's quite a few steps to take, and we'll have a much easier time of it if we tackle them one at a time. For this project, we're going to start with step 3 ( mapping the data ), then take care of step 4 (rendering the page correctly). Finally, we'll tackle steps 1 and 2 which involve interaction with the API itself.

Curl

In order to start with step 3 above, we'll need to have a copy of the real data from the API saved somewhere. So, we'll make a request from the command line to the API, and save the response to a file. Then, while we're developing, we can use the saved file instead of worrying about the complexity of making an API call on every request. A good analogy is the scaffolding you may use when building a building. During construction, you have scaffolds setup around it to support the structure while you work. Eventually, you'll take the scaffolding down, and the building will stand on its own.

Saving an API response to a file

Our scaffolding for this project is a cached response from the API, and we'll use a Unix command called 'curl' to fetch it for us. Curl is run from the terminal, and can easily save data from a web request to a file. You can read all about curl on its man page, man curl.

The command looks like this:

curl "https://api.nasa.gov/neo/rest/v1/feed?start_date=2017-7-31&api_key=api_key" > sample-neo.js

That will save a file on disk of the exact response that we get from the API.

*Note: If you are following along with the video, we will start using this sample-neo.js from inside the src folder. Go ahead and move it into src.

Working with cached files in React components

An easy way to work with cached data in our component is to assign it to a Javascript object and export it. Then we import it right into our component.

In the cached file /src/sample-neo.js

export default {
  ... response from curling the API request
}

Then, in the component /src/App.js

import neoData from './sample-neo'

Now, in the constructor of the component, we can assign the cached data to the state of the component:

class App extends Component {
  constructor(props){
    super(props)
    this.state = {
      rawData: neoData
    }
  }

  render(){
    ....
  }
}

Challenges

  • Use Curl to save a file with the response from a request to the NEO api.
  • Use Curl to save a file with the response from the International Space Station Location api: http://api.open-notify.org/iss-now.json
  • Build a new React app using create-react-app, export and import your Space Station file, and assign the space stations latitude and longitude to the App component's state. Use console.log or a debugger statement to verify that it is working.

04 API Data from LEARN on Vimeo

API Data

Working with third-party data requires us to parse the data we receive and transform it into something useful for the application. Depending on how complex the API is, this can be somewhat challenging, and takes some careful consideration on our part to do efficiently. In the NEO API response, the asteroid data comes in a different format than how we want to use it in our HTML table, so we have to map it to a structure that makes sense for us and our app.

This is where having the saved file to use as a reference as we're developing provides a lot of value. Instead of making a call across the web to the API, we can just load the file, experiment, and work with the data until we get exactly what we want. Not only does this save us a lot of time, it reduces strain on NASA's servers, which they will undoubtedly appreciate.

Mapping API Data

Consider the following structure of the NEO API response:

{
  "links" : {
    "next" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2017-08-07&end_date=2017-08-14&detailed=false&api_key=NT8V130OXGjNIRbuEbvygKwFipek7WxYXI8nn1o9",
    "prev" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2017-07-24&end_date=2017-07-31&detailed=false&api_key=NT8V130OXGjNIRbuEbvygKwFipek7WxYXI8nn1o9",
    "self" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2017-07-31&end_date=2017-08-07&detailed=false&api_key=NT8V130OXGjNIRbuEbvygKwFipek7WxYXI8nn1o9"
  },
  "element_count" : 49,
  "near_earth_objects" : {
    "2017-08-05" : [ {
      "links" : {
        "self" : "https://api.nasa.gov/neo/rest/v1/neo/3102785?api_key=NT8V130OXGjNIRbuEbvygKwFipek7WxYXI8nn1o9"
      },
      "neo_reference_id" : "3102785",
      "name" : "(2002 BG25)",
      "nasa_jpl_url" : "http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=3102785",
      "absolute_magnitude_h" : 20.9,
      "estimated_diameter" : {
        "kilometers" : {
          "estimated_diameter_min" : 0.1756123185,
          "estimated_diameter_max" : 0.3926810818
        },
        "meters" : {
          "estimated_diameter_min" : 175.6123184804,
          "estimated_diameter_max" : 392.6810818086
        },
        "miles" : {
          "estimated_diameter_min" : 0.1091204019,
          "estimated_diameter_max" : 0.2440006365
        },
        "feet" : {
          "estimated_diameter_min" : 576.1559189633,
          "estimated_diameter_max" : 1288.3238004408
        }
      },
      "is_potentially_hazardous_asteroid" : false,
      "close_approach_data" : [ {
        "close_approach_date" : "2017-08-05",
        "epoch_date_close_approach" : 1501916400000,
        "relative_velocity" : {
          "kilometers_per_second" : "24.5177734759",
          "kilometers_per_hour" : "88263.9845133905",
          "miles_per_hour" : "54843.8074883342"
        },
        "miss_distance" : {
          "astronomical" : "0.3164080673",
          "lunar" : "123.0827407837",
          "kilometers" : "47333972",
          "miles" : "29411968"
        },
        "orbiting_body" : "Earth"
      } ]
    }, {
      "links" : {
        "self" : "https://api.nasa.gov/neo/rest/v1/neo/3276601?api_key=NT8V130OXGjNIRbuEbvygKwFipek7WxYXI8nn1o9"
      },
      "neo_reference_id" : "3276601",
      "name" : "(2005 GB120)",
      "nasa_jpl_url" : "http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=3276601",
      "absolute_magnitude_h" : 20.4,
      "estimated_diameter" : {
        "kilometers" : {
          "estimated_diameter_min" : 0.2210828104,
          "estimated_diameter_max" : 0.4943561926
        },
        "meters" : {
          "estimated_diameter_min" : 221.0828103591,
          "estimated_diameter_max" : 494.3561926196
        },
        "miles" : {
          "estimated_diameter_min" : 0.137374447,
          "estimated_diameter_max" : 0.3071786018
        },
        "feet" : {
          "estimated_diameter_min" : 725.3373275385,
          "estimated_diameter_max" : 1621.9035709942
        }
      },
      "is_potentially_hazardous_asteroid" : false,
      "close_approach_data" : [ {
        "close_approach_date" : "2017-08-05",
        "epoch_date_close_approach" : 1501916400000,
        "relative_velocity" : {
          "kilometers_per_second" : "8.6908491074",
          "kilometers_per_hour" : "31287.056786697",
          "miles_per_hour" : "19440.5603683785"
        },
        "miss_distance" : {
          "astronomical" : "0.3528578146",
          "lunar" : "137.2616882324",
          "kilometers" : "52786780",
          "miles" : "32800184"
        },
        "orbiting_body" : "Earth"
      } ]
    },
    .....
  }
}

In this response, we have 3 top level attributes: links, element_count, near_earth_objects. The section we're interested in is the 'near_earth_objects' part, so the first thing we can do is assign that part to a variable, and ignore the rest. Once we have hold of what we want, we can iterate over it and map the data into a new, more useful, data structure:

componentWillMount(){
    // Get hold of the part of the response we are interested in
    let neoData = this.state.rawData.near_earth_objects

    // Instantiate a new array to hold our mapped data
    let newAsteroids = []

    // To iterate of attributes of a JS Object, we call: Object.keys()
    Object.keys(neoData).forEach((date)=>{

      // Object.keys returns the name of the attribute, so we need to access that attribute
      // on our data structure, and loop over each asteroid it contains
      neoData[date].forEach((asteroid) =>{

        // Now that we have the asteroid, we can add it to our newAsteroids array
        newAsteroids.push({
          id: asteroid.neo_reference_id,
          name: asteroid.name,
          date: asteroid.close_approach_data[0].close_approach_date,

          // Calling '.toFixed(0)' on a float cuts off everything behind the decimal point.
          // This formats the information for a good user experience.
          diameterMin: asteroid.estimated_diameter.feet.estimated_diameter_min.toFixed(0),
          diameterMax: asteroid.estimated_diameter.feet.estimated_diameter_max.toFixed(0),
          closestApproach: asteroid.close_approach_data[0].miss_distance.miles,
          velocity: parseFloat(asteroid.close_approach_data[0].relative_velocity.miles_per_hour).toFixed(0),
          distance: asteroid.close_approach_data[0].miss_distance.miles
        })
      })
    })

    // Finally, now that we have collected all the asteroids, we can assign them to state
    // so that we can use them later on in the render function
    this.setState({asteroids: newAsteroids})
}

*Notes:

  • Here we are setting the value of state's asteroid property. If this.state.asteroid is coming up undefined, check to see that state contains an asteroid property.

Challenges

  • create a React app (or reuse the app from the previous challenge) and import a cached API response object from the NEO Api
  • create a new React app, and curl the following API request into a sample data file: https://api.nasa.gov/neo/rest/v1/neo/2465633?api_key=
  • Import the data file, and add the following attributes to the component state:
    • reference_id
    • name
    • diameter_in_feet
    • closest_approach_dates: (this is a list of all the known close approaches for this asteroid to earth)

05 Table Data from LEARN on Vimeo

Table Data

Now that we have an array of Asteroids in the component's state, we're ready to display the data in our table.

Iterating over an array in JSX

JSX is a powerful tool to build complex HTML structures. JSX is actually just Javascript that allows HTML to be mixed in, so we can loop, add conditionals, and do many other logical things to our markup. In this case, we're going to be looping over an array:

{this.state.asteroids.map((asteroid)=>{
  return(
    .. any HTML we want
  )
}

This structure renders the array of HTML markup objects returned from the map operation on our array.

Here is the complete table:

<Table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Estimated Diameter (feet)</th>
      <th>Date of Closest Approach</th>
      <th>Distance (miles)</th>
      <th>Velocity (miles/hour)</th>
    </tr>
  </thead>
  <tbody>
    {this.state.asteroids.map((asteroid)=>{
      return(
        <tr key={asteroid.id}>
          <td>{asteroid.name}</td>
          <td>{asteroid.diameterMin} - {asteroid.diameterMax}</td>
          <td>{asteroid.date}</td>
          <td>{asteroid.distance}</td>
          <td>{asteroid.velocity}</td>
        </tr>
      )
    })}
  </tbody>
</Table>

Challenges

  • Create a new React app (or reuse one from previous challenges) and load sample data from the NEO api
  • Iterate over the mapped data and represent it as a table on your web page

06 Fetch from LEARN on Vimeo

Fetching data from live API

Fetch is a built in interface to pull data or files into our application from somewhere on the internet. Fetch uses promises to handle the asynchronous requests. In its simplest form, fetch works like this:

fetch(<url>).then((rawResponse)=>{
  //return the part of the response we're interested in
  return rawResponse.json()
}).then(parsedResponse) => {
  //do what we need to with the parsedResponse, a JS object in this case
})

So, for our example, we need to make the request out to the NEO api, and handle the response, converting the response into an array of asteroids.

First, let's update our component's state with our API key and other properties we'll need to use in our API call.

class App extends Component {
  constructor(props){
    super(props)
    let today = new Date()
    this.state = {
      startDate:`${today.getFullYear()}-${today.getMonth()+1}-${today.getDate()}`,
      rawData: sampleNeo,
      asteroids: []
    }
}

Here is the completed componentWillMount method:

componentWillMount(){
  fetch("https://api.nasa.gov/neo/rest/v1/feed?"+`start_date=${this.state.startDate}&api_key=${<your apikey>}`).then((rawResponse)=>{
    // rawResponse.json() returns a promise that we pass along
    return rawResponse.json()
  }).then((parsedResponse) => {

    // when this promise resolves, we can work with our data
    let neoData = parsedResponse.near_earth_objects

    let newAsteroids = []
    Object.keys(neoData).forEach((date)=>{
      neoData[date].forEach((asteroid) =>{
        newAsteroids.push({
          id: asteroid.neo_reference_id,
          name: asteroid.name,
          date: asteroid.close_approach_data[0].close_approach_date,
          diameterMin: asteroid.estimated_diameter.feet.estimated_diameter_min.toFixed(0),
          diameterMax: asteroid.estimated_diameter.feet.estimated_diameter_max.toFixed(0),
          closestApproach: asteroid.close_approach_data[0].miss_distance.miles,
          velocity: parseFloat(asteroid.close_approach_data[0].relative_velocity.miles_per_hour).toFixed(0),
          distance: asteroid.close_approach_data[0].miss_distance.miles
        })
      })
    })

    // state is updated when promises are resolved
    this.setState({asteroids: newAsteroids})
  })
}

Challenges

  • Update the challenges you've worked on so far to pull live data from the API

Today's Tentative Schedule

9:15am - Stand Up

9:30am - SQL in Terminal: Active Record, Naming Conventions and One-to-Many Relationships

11:00am - Challenge: Task List with SQL

12:00 noon - Lunch

1:00pm - continue with Task List with SQL Challenge

4.30pm - Review

5:00pm - Class Ends