This post is part of the series 30 Days of React.

In this series, we're starting from the very basics and walk through everything you need to know to get started with React. If you've ever wanted to learn React, this is the place to start!

Displaying Remote Data

Edit this page on Github

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

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.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.bind(this)}>
            Get the current time
          </button>}
        {currentTime && <div>The current time is: {currentTime}</div>}
        <TimeForm
          onFormSubmit={this.handleFormSubmit.bind(this)}
          onFormChange={this.handleChange.bind(this)}
          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. Our TimeForm component might look like the following code.:

import React from 'react'
const timezones = ['PST', 'MST', 'MDT', 'EST', 'UTC']

export class TimeForm extends React.Component {
  constructor(props) {
    super(props);

    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/, '+');
    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.bind(this)}>
        <select
          onChange={this._changeTimezone.bind(this)}
          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.bind(this)}
        />
        <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 heroku-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'
    }
  }
  // ...
  getApiUrl() {
    const {tz, msg} = this.state;
    const host = 'https://fullstacktime.herokuapp.com';
    return `${host}/${tz}/${msg}.json`;
  }

}

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.


Ari Lerner

Hi, I'm Ari. I'm the author of ng-book and I've been teaching Web Development for a long time. I like to speak at conferences and eat spicy food. I technically got paid while I traveled the country as a professional comedian, but have come to terms with the fact that I am not funny.

Connect with Ari on Twitter at @auser.