Skip to content

Latest commit

 

History

History
 
 

08-api

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Step 8 - Interacting with APIs

The goal of this step is to move away from the hard-coded EMAILS constant to interacting with a "real" API server using the Fetch API and ES6 Promises. We'll retrieve the data in the App component's lifecyle methods and store in its state.

As always, if you run into trouble with the tasks or exercises, you can take a peek at the final source code.

Restart Setup

If you didn't successfully complete the previous step, you can jump right in by copying the step and installing the dependencies.

Ensure you're in the root folder of the repo:

cd react-workshop

Remove the existing workshop directory if you had previously started elsewhere:

rm -rf workshop

Copy the previous step as a starting point:

cp -r 07-delete-email workshop

Change into the workshop directory:

cd workshop

Install all of the dependencies (yarn is preferred):

# Yarn
yarn

# ...or NPM
npm install

Start the app:

# Yarn
yarn start

# ...or NPM
npm start

After the app is initially built, a new browser window should open up at http://localhost:3000/, and you should be able to continue on with the tasks below.

API Setup

If you successfully completed the previous step, you just need to start the API server. In a separate terminal window/tab, making sure you're still in the workshop directory, start API server (running at http://localhost:9090/):

# Yarn
yarn run start:api

# NPM
npm run start:api

Verify that you receive a JSON response from http://localhost:9090/emails.

Tasks

In the App component add the componentDidMount() lifecycle method and make a GET fetch request to http://localhost:9090/emails:

export default class App extends PureComponent {
  componentDidMount() {
    // Retrieve emails from server once we know DOM exists
    fetch('//localhost:9090/emails');
  }

  render() {
    return (
      <main className="app">
        <EmailList emails={EMAILS} />
        <EmailView />
        <EmailForm />
      </main>
    );
  }
}

componentDidMount() is invoked immediately after a component is mounted (added to the DOM). Initialization that requires DOM nodes or API requests to load data go here.

We can replace the use of the EMAILS constant with data from the API. Initialize the state to empty ([]), store the result of the fetch call in the App component's state, and remove the EMAILS constant altogether:

export default class App extends PureComponent {
  state = {
    // Initialize emails state to an empty array.
    // Will get populated with data in `componentDidMount`
    emails: [],
    // Initialize selected email ID to -1, indicating nothing is selected.
    // When an email is selected in EmailList, this will be updated to
    // corresponding ID
    selectedEmailId: -1
  }

  componentDidMount() {
    // Retrieve emails from server once we know DOM exists
    fetch('//localhost:9090/emails')
      .then(res => res.json())
      // update the state with the emails fetched from the server
      .then(emails => this.setState({emails}))
      .catch(ex => console.error(ex));
  }

  // render()
}

Initially you'll see an empty list of emails, but soon after a list of 50 emails instead of the original five. Our app is looking more like a legitimate app.

Next let's add long-polling to App so that we're periodically checking for any new emails that may have been added (or deleted):

export default class App extends PureComponent {
  state = {
    // Initialize emails state to an empty array.
    // Will get populated with data in `componentDidMount`
    emails: [],
    // Initialize selected email ID to -1, indicating nothing is selected.
    // When an email is selected in EmailList, this will be updated to
    // corresponding ID
    selectedEmailId: -1
  }

  componentDidMount() {
    // Retrieve emails from server once we know DOM exists
    this._getUpdateEmails();

    // Set up long-polling to continuously get new data every 2 seconds
    this._pollId = setInterval(
      () => this._getUpdateEmails(),
      2000
    );
  }

  componentWillUnmount() {
    // Need to remember to clearInterval when the component gets
    // removed from the DOM, otherwise the interval will keep going
    // forever and leak memory
    clearInterval(this._pollId);
  }

  _getUpdateEmails() {
    return fetch('//localhost:9090/emails')
      .then(res => res.json())
      .then(emails => this.setState({emails}))
      .catch(ex => console.error(ex));
  }

  // helper methods

  // render()
}

We leveraged setInterval in componentDidMount() to kick off the long-polling after loading the initial data. We also used clearInterval in componentWillUnmount() to clean up after ourselves and stop polling whenever App is removed from the DOM.

We can communicate the email creations & deletions with the API since the server should be the source of truth. Let's start by updating _handleFormSubmit() to make an HTTP POST with the new email info:

export default class App extends PureComponent {
  state = {
    // Initialize emails state to an empty array.
    // Will get populated with data in `componentDidMount`
    emails: [],
    // Initialize selected email ID to -1, indicating nothing is selected.
    // When an email is selected in EmailList, this will be updated to
    // corresponding ID
    selectedEmailId: -1
  }

  // componentDidMount()

  // other helper methods

  _handleFormSubmit(newEmail) {
    // Make a JSON POST with the new email
    fetch('//localhost:9090/emails', {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(newEmail)
    })
      .then(res => res.json())
      .then(({success}) => {
        if (!success) {
          throw new Error('Unable to send email!');
        }
      })
      .catch(ex => console.error(ex));
  }

  // render()
}

You should now be able to fill out the email form, click "Send email", and within 2 seconds see the new email added at the top of the list. The maximum 2-second delay is due to the long-polling we set up. It's the only way that this.state.emails is update with the latest data.

Exercises

  • Update _handleItemDelete() to make an HTTP DELETE request to //localhost:9090/emails/<EMAIL_ID> to delete the unwanted email
  • Add pollInterval prop to App (with the appropriate prop type defined) that defaults to 2000 (using defaultProps), but index.js overrides passing in 5000 to <App />
  • After POSTing the new email in _handleFormSubmit(), "optimistically update" this.state.emails with the new email so that the new email shows up immediately in the email list before the long poll interval comes around (HINT: code should be very similar to code prior to making API POST)
  • Similarly after DELETEing the email in _handleItemDelete() "optimistically update" this.state.emails so that the deleted email is removed immediately removed from the list before the long poll interval comes around

Next

Go to Step 9 - Mark unread/read.

Resources

Questions

Got questions? Need further clarification? Feel free to post a question in Ben Ilegbodu's AMA!