Intro to AJAX and Rendering the Data with React
#
Learning Objectives- Describe what is AJAX
- Describe what AJAX is used for
- Describe what are 3rd Party APIs and How they are used
- Describe what are API keys and how to use them
- Review Query Parameters and how they can be used in 3rd Party API requests
- Set up fetch for a React App
- Learn how to use fetch to make API requests
- Learn how to incorporate data from fetch requests into a SPA using React
#
AJAXAJAX stands for Asynchronous JavaScript an XML.
XML was once a popular way to store and send data over the internet and it is still used. However, JSON has become the predominant way to send data over the internet. But, no one seems to want to change the acronym AJAX to AJAJ.
When we will use AJAX, we will be sending and receiving JSON.
AJAX allows us to only reload portions of a web page. Thinking of an embedded google map, you can click around it and change the view/data, without having to reload the page every single time.
#
AJAX/HTTP RequestsAt first, jQuery was the only 'friendly' way to make AJAX requests. Over time, new libraries have developed.
Some top choices
fetch
built in to most modern browsers- axios - Axios has a few built in security features (examples use promises)
- Ky - A small lightweight library with examples using async/await
#
Third Party APIsMany web sites have their own data, but they can pull in other data. For example, many news sites have a weather widget. This widget gets its data from a weather resource.
There are many APIs that can be used by individuals and companies. Some are totally free, some are available for a small fee, and some are really expensive.
There are APIs for
- Weather
- Stocks
- Beer
- Dictionaries
- Books
- Sports
- Art
- Games
- Movies
#
API KeysMany APIs are restricted. Maintaining data on a server can get expensive and the data on a lot of these sites is valuable.
The two main ways individuals/companies can get access to APIs is through API keys - a special set of characters that is purchased through the website. Every time you make a request, the key must be used, this lets the API keep track of how many requests you make and limit/charge you accordingly.
The other way is OAuth. OAuth is a tangent to what we'll talk about today, but if you want to learn more, here is a good start.
Typically, API keys go in as query parameters. Query parameters go at the end of a URL. They start with a ?
and then have key value pairs. Many key-value pairs can be used and each key-value pair can be used by separating them with an equal sign.
Here is an example of a request to OMDB (open movie data base), for a movie with the title Eraserhead
and the apikey
(this is a fake key).
http://www.omdbapi.com/?t=Eraserhead&apikey=x9903Ddc
A response in JSON will be sent and could be seen in the browser
#
Mini Movie AppWe're going to build a tiny React single page app that has a text input, a button and when a user inputs a movie, they will get a set of movies that match from OMDB.
#
SetupIn student_examples
npx create-react-app movies
cd movies
rm -rf .git
(because we are already in a repo)
#
App.jsclean it up so we have a blank slate
import React, { Component } from "react";
class App extends Component { render() { return <div></div>; }}
export default App;
#
Setting Up Our FormWe're going to be making requests to OMDB using fetch
. We'll be viewing those results in Chrome's Console. Once we build out the functionality, we'll start incorporating our data into our web page.
Go to request an api key from OMDB
Fill out the form, you should get an email asking you to validate your key within a few minutes. Hold on to this key, we'll be using it soon enough.
An http request requires:
- headers (fetch handles most of this for us)
- a url to send the request
- a method (get, post, put, delete...)
- a way to send data if it is a post or put request
- a way to wait for the response before doing something with the incoming data (We'll use promises, but there is a newer way with async/await that you can research on your own)
- a way to do something with the incoming data
- a way to handle errors
Let's build our first fetch request.
fetch
is a function. it takes one argument which MUST be an object. We'll code this out together after we put together our query string
The object requires a minimum of two key value pairs
method
- a string:GET
,POST
,PUT
,DELETE
url
- a string of where to send the requestif the request is sending data (ie input from input fields), a third key value pair is required
data
: an object of key value pairs Since we are just sending GET requests, we won't needdata
for now - We'll build out ourfetch
request after we set up our form.
#
app.jsLet's build our form and form functionality. We've done this before and it seems like a lot of boilerplate functionality. Because react has reusable components, you could just build this functionality once per project. But you may find that you want different behaviors for different inputs.
We're going to break our request into multiple parts and then concatenate it. This will help us see each piece and what it does, and then possibly, help us have more flexibility as we build more complex/varied requests
baseURl
:'http://www.omdbapi.com/?'
the start of our request beginning withhttp://
, after the last/
we have a question mark, that will start our query parametersapikey
:'apikey='
+'your api key here'
apikey
is the specific key OMDB is looking for, then we'll add our own api key (no spaces).query
:'&t='
the ampersand&
lets us know that there is another query parameter coming up.t=
is the next key, it matches the type of search we want to do. There are a few searches in the OMDB documentation. Perhaps later on, we'd want to use a different one?.movieTitle
:''
- the movie title we'd like to search for. We'll be able to enter what we want using our input and formsearchURL:
''
- here we'll end up concatenating all the pieces to make a working url.
constructor(props) { super(props); this.state = { baseURL: "http://www.omdbapi.com/?", apikey: "apikey=" + "98e3fb1f", query: "&t=", movieTitle: "", searchURL: "", };}
Next we'll have to add our form(and we'll add an anchor tag at the bottom, if we've successfully built our URL, we should be able to click on it and see our JSON from OMBD):
render() { return ( <React.Fragment> <form onSubmit={this.handleSubmit}> <label htmlFor="movieTitle">Title</label> <input id="movieTitle" type="text" value={this.state.movieTitle} onChange={this.handleChange} /> <input type="submit" value="Find Movie Info" /> </form> <a href={this.state.searchURL}>{this.state.searchURL}</a> </React.Fragment> );}
Finally, we have to add our handleChange
and handleSubmit
functionality. And let's not forget to bind those in our constructor
constructor(props) { super(props); this.state = { baseURL: "http://www.omdbapi.com/?", apikey: "apikey=" + "98e3fb1f", query: "&t=", movieTitle: "", searchURL: "", }; this.handleSubmit = this.handleSubmit.bind(this); this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({ [event.target.id]: event.target.value }); } handleSubmit(event) { event.preventDefault(); this.setState({ searchURL: this.state.baseURL + this.state.apikey + this.state.query + this.state.movieTitle, }); }
our searchURL should look like (the api key is fake, it should be the one you got via email).
http://www.omdbapi.com/?apikey=9999999&t=Eraserhead
When you click on your anchor tag, you should be taken to the JSON view:
Note: including your API key in the app.js
and then pushing it up to github makes your API key findable. OMDB keys are not that valuable, so it shouldn't be a worry.
However, there are services that cost thousands of dollars a month. People write bots to look for exposed API keys to steal them. We don't have time to cover hiding API keys today. But keep it in mind for your projects.
#
Using FetchRather than render the a
tag, we want to be able display the data we want on on our web page. Let's start out by getting our JSON from OMDB to console log. When we are doing a get request, all we need to do is put a string as our first argument.
We have a one little GOTCHA - we have to make sure that state has been set, before we make our request. So we'll add our fetch request as a second argument in our setState
function inside our handleSubmit
handleSubmit(event) { event.preventDefault(); this.setState( { searchURL: this.state.baseURL + this.state.apikey + this.state.query + this.state.movieTitle, }, () => { // fetch request will go here } );}
Let's isolate and look at each part of our fetch request as we build it inside our callback of setState
#
fetch functionfetch()
#
fetch function with url string as argumentfetch(this.state.searchURL)
Remember, JavaScript just runs through the code top to bottom. It doesn't wait for one thing to finish before moving on to the next thing.
One way we've been handling waiting is by using callbacks (like we just did with setState
). Callbacks can get unwieldy. Promises can be a nicer way to write our code. Often you'll be working with promises that were already created for you from a library or framework (like fetch
)
A promise is a way of writing code that says 'hey, wait for me, and I promise I'll send you a response soon. Then, you can do what you need to do'
A promise has three states
- Pending
- Fulfilled (completed successfully)
- Rejected (it failed)
After calling the initial function (in our case fetch()
), all we need to do is chain another function .then()
.then()
takes two callback functions.
The first one is onFulfilled
this one is executed if the promise is fulfilled. This function can do whatever we want, for now, we'll just console.log the response.
The second one is onRejected
this one is executed instead of the first one if the promise has failed in some way.
Then
we need to convert our response to JSON
fetch(this.state.searchURL).then((response) => response.json());
Then we will console log our resonse. We can break our then()
onto multiple lines to make it easier to read(())
fetch(this.state.searchURL) .then((response) => { return response.json(); }) .then( (json) => console.log(json), (err) => console.log(err) );
Our error handling can be very simple, we can just console log the error.
The entire handleSubmit
function
function handleSubmit(event) { event.preventDefault(); this.setState( { searchURL: this.state.baseURL + this.state.apikey + this.state.query + this.state.movieTitle, }, () => { console.log(this.state.searchURL); fetch(this.state.searchURL) .then((response) => { return response.json(); }) .then( (json) => console.log(json), (err) => console.log(err) ); } );}
This will send our request, but now we have to handle the response. The fetch function returns a promise. A promise is an object that handles asynchronous operations.
fetch({ method: "GET", url: this.searchURL,}).then((response) => { console.log(response);});
Expected Appearance:
#
Rendering our response in the BrowserLet's make a movie component that will render a view of our movie
class MovieInfo extends Component { render() { return ( <div> <h1>Title: {this.props.movie.Title}</h1> <h2>Year: {this.props.movie.Year}</h2> <img src={this.props.movie.Poster} alt={this.props.movie.Title} /> <h3>Genre: {this.props.movie.Genre}</h3> <h4>Plot: {this.props.movie.Plot}</h4> </div> ); }}
Gotcha! - the data that came back to us from OMDB set the keys to have a capital letter- we need to make sure we match our data
Now, instead of console.logging our data, let's set the state and clear the form
handleSubmit(event) { event.preventDefault(); this.setState( { searchURL: this.state.baseURL + this.state.apikey + this.state.query + this.state.movieTitle, }, () => { fetch(this.state.searchURL) .then((response) => { return response.json(); }) .then( (json) => this.setState({ movie: json, movieTitle: "", }), (err) => console.log(err) ); } );}
Finally, let's swap out our a
tag with our component and pass in our movie data as props we'll render nothing if there is no data.
render() { return ( <React.Fragment> <form onSubmit={this.handleSubmit}> <label htmlFor="movieTitle">Title</label> <input id="movieTitle" type="text" placeholder="Movie Title" onChange={this.handleChange} /> <input type="submit" value="Find Movie Info" /> </form> {this.state.movie ? <MovieInfo movie={this.state.movie} /> : ""} </React.Fragment> );}
#
Entire code#
App.jsimport React, { Component } from "react";import MovieInfo from "./components/MovieInfo.js";
class App extends Component { constructor(props) { super(props); this.state = { baseURL: "http://www.omdbapi.com/?", apikey: "apikey=" + "98e3fb1f", query: "&t=", movieTitle: "", searchURL: "", }; this.handleSubmit = this.handleSubmit.bind(this); this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({ [event.target.id]: event.target.value }); } handleSubmit(event) { event.preventDefault(); this.setState( { searchURL: this.state.baseURL + this.state.apikey + this.state.query + this.state.movieTitle, }, () => { fetch(this.state.searchURL) .then((response) => { return response.json(); }) .then( (json) => this.setState({ movie: json, movieTitle: "", }), (err) => console.log(err) ); } ); } render() { return ( <React.Fragment> <form onSubmit={this.handleSubmit}> <label htmlFor="movieTitle">Title</label> <input id="movieTitle" type="text" value={this.state.movieTitle} onChange={this.handleChange} /> <input type="submit" value="Find Movie Info" /> </form> {this.state.movie ? <MovieInfo movie={this.state.movie} /> : ""} </React.Fragment> ); }}
export default App;
#
MovieInfo.jsimport React, { Component } from "react";
class MovieInfo extends Component { render() { return ( <div> <h1>Title: {this.props.movie.Title}</h1> <h2>Year: {this.props.movie.Year}</h2> <img src={this.props.movie.Poster} alt={this.props.movie.Title} /> <h3>Genre: {this.props.movie.Genre}</h3> <h4>Plot: {this.props.movie.Plot}</h4> </div> ); }}
export default MovieInfo;