Our front-end applications are only as interesting as the data we display in them. Today, let's actually start making a request for data and get it integrated into our app.
As of today, we've worked through promises, built our app using the npm
packager, installed our remote object fetching library (whatwg-fetch
) and we're finally ready to integrate remote data into our application.
Fetching data
Let's get into using the fetch
library we installed on day 14.
For simplicity purposes, let's break out our demo from yesterday where we fetched the current time from an API server:
This demo react component makes a request to the API server and reports back the current time from it's clock. Before we add the call to fetch, let's create a few stateful components we'll use to display the time and update the time request.
Walls of code warning
We realize the next few lines are walls of code, which we generally try to avoid, especially without discussing how they work. However, since we're not talking about how to create a component in detail here, yet we still want to fill out a complete component, we've made an exception.
Please leave us feedback (links at the bottom) if you prefer us to change this approach for today.
First, the basis of the wrapper component which will show and fetch the current time looks like the following. Let's copy and paste this code into our app at src/App.js
:
import React from 'react';
import 'whatwg-fetch';
import './App.css';
import TimeForm from './TimeForm';
class App extends React.Component {
constructor(props) {
super(props);
this.fetchCurrentTime = this.fetchCurrentTime.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.state = {
currentTime: null, msg: 'now'
}
}
// methods we'll fill in shortly
fetchCurrentTime() {}
getApiUrl() {}
handleFormSubmit(evt) {}
handleChange(newState) {}
render() {
const {currentTime, tz} = this.state;
const apiUrl = this.getApiUrl();
return (
<div>
{!currentTime &&
<button onClick={this.fetchCurrentTime}>
Get the current time
</button>}
{currentTime && <div>The current time is: {currentTime}</div>}
<TimeForm
onFormSubmit={this.handleFormSubmit}
onFormChange={this.handleChange}
tz={tz}
msg={'now'}
/>
<p>We'll be making a request from: <code>{apiUrl}</code></p>
</div>
)
}
}
export default App;
The previous component is a basic stateful React component as we've created. Since we'll want to show a form, we've included the intended usage of the TimeForm
let's create next.
Let's create this component in our react app using create-react-app
. Add the file src/TimeForm.js
into our project:
touch src/TimeForm.js
Now let's add content. We'll want our TimeForm
to take the role of allowing the user to switch between timezones in their browser. We can handle this by creating a stateful component we'll call the TimeForm
. Our TimeForm
component might look like the following:
import React from 'react'
const timezones = ['PST', 'MST', 'MDT', 'EST', 'UTC']
export class TimeForm extends React.Component {
constructor(props) {
super(props);
this._changeTimezone = this._changeTimezone.bind(this);
this._handleFormSubmit = this._handleFormSubmit.bind(this);
this._handleChange = this._handleChange.bind(this);
this._changeMsg = this._changeMsg.bind(this);
const {tz, msg} = this.props;
this.state = {tz, msg};
}
_handleChange(evt) {
typeof this.props.onFormChange === 'function' &&
this.props.onFormChange(this.state);
}
_changeTimezone(evt) {
const tz = evt.target.value;
this.setState({tz}, this._handleChange);
}
_changeMsg(evt) {
const msg =
encodeURIComponent(evt.target.value).replace(/%20/g, '+');
this.setState({msg}, this._handleChange);
}
_handleFormSubmit(evt) {
evt.preventDefault();
typeof this.props.onFormSubmit === 'function' &&
this.props.onFormSubmit(this.state);
}
render() {
const {tz} = this.state;
return (
<form onSubmit={this._handleFormSubmit}>
<select
onChange={this._changeTimezone}
defaultValue={tz}>
{timezones.map(t => {
return (<option key={t} value={t}>{t}</option>)
})}
</select>
<input
type="text"
placeholder="A chronic string message (such as 7 hours from now)"
onChange={this._changeMsg}
/>
<input
type="submit"
value="Update request"
/>
</form>
)
}
}
export default TimeForm;
With these Components created, let's load up our app in the browser after running it with npm start
and we'll see our form (albeit not incredibly beautiful yet). Of course, at this point, we won't have a running component as we haven't implemented our data fetching. Let's get to that now.
Fetching data
As we said yesterday, we'll use the fetch()
API with promise support. When we call the fetch()
method, it will return us a promise, where we can handle the request however we want. We're going to make a request to our now-based API server (so start-up might be slow if it hasn't been run in a while).
We're going to be building up the URL we'll request as it represents the time query we'll request on the server.
I've already defined the method getApiUrl()
in the App
component, so let's fill that function in.
The chronic api server accepts a few variables that we'll customize in the form. It will take the timezone to along with a chronic message. We'll start simply and ask the chronic library for the pst
timezone and the current time (now
):
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentTime: null, msg: 'now', tz: 'PST'
}
}
// ...
getApiUrl() {
const {tz, msg} = this.state;
const host = 'https://andthetimeis.com';
return host + '/' + tz + '/' + msg + '.json';
}
// ...
export default App;
Now, when we call getApiUrl()
, the URL of the next request will be returned for us. Now, finally, let's implement our fetch()
function. The fetch()
function accepts a few arguments that can help us customize our requests. The most basic GET
request can just take a single URL endpoint. The return value on fetch()
is a promise object, that we explored in-depth yesterday.
Let's update our fetchCurrentTime()
method to fetch the current time from the remote server. We'll use the .json()
method on the response object to turn the body of the response from a JSON object into JavaScript object and then update our component by setting the response value of the dateString
as the currentTime
in the component state:
class App extends React.Component {
// ...
fetchCurrentTime() {
fetch(this.getApiUrl())
.then(resp => resp.json())
.then(resp => {
const currentTime = resp.dateString;
this.setState({currentTime})
})
}
// ...
}
The final piece of our project today is getting the data back from the form to update the parent component. That is, when the user updates the values from the TimeForm
component, we'll want to be able to access the data in the App
component. The TimeForm
component already handles this process for us, so we just need to implement our form functions.
When a piece of state changes on the form component, it will call a prop called onFormChange
. By defining this method in our App
component, we can get access to the latest version of the form.
In fact, we'll just call setState()
to keep track of the options the form allows the user to manipulate:
class App extends React.Component {
// ...
handleChange(newState) {
this.setState(newState);
}
// ...
}
Finally, when the user submits the form (clicks on the button or presses enter in the input field), we'll want to make another request for the time. This means we can define our handleFormSubmit
prop to just call the fetchCurrentTime()
method:
class App extends React.Component {
// ...
handleFormSubmit(evt) {
this.fetchCurrentTime();
}
// ...
}
Try playing around with the demo and passing in different chronic options. It's actually quite fun.
In any case, today we worked on quite a bit to get remote data into our app. However, at this point, we only have a single page in our single page app. What if we want to show a different page in our app? Tomorrow, we're going to start adding multiple pages in our app so we can feature different views.