Fetching data

Fetching data
WIP

You’re nearly there. You’ve come from creating a simple list of contacts in a single html file, to creating an entire contact list, with a smooth UX to boot. There’s just one problem.

When you refresh the page, all of your new data disappears.

Real apps need to read and write data.

Now to put it bluntly, managing data in React apps is not something that we can cover in one lesson. You could probably write a book on it. But you have to start somewhere. And what better place to start than loading data for your contact list?

#Async data

When working with JavaScript, data is always fetched asynchronously. Which means that when you call fetch() to ask a HTTP server for some data, execution continues as if nothing happened. And then when the response is received, the browser will go back and execute the response handler that you supplied.

index.js
fetch.js
import { fetch } from './fetch.js'

console.log('1. Start!')

let promise = fetch('https://frontarm.com/example')

promise.then(data => {
  console.log('3. Wait a minute! I have data now!', data)
})

console.log('2. Finish!')

    If you’re just starting out, this can be a bit of a surprise. Asynchronous code doesn’t execute in order! And even if you’ve been working with asynchronous code for years, figuring out exactly what to do with the received data can be a chore.

    Luckily though, React makes handling received data easy. In fact, you already know how to handle received data. Because handling HTTP responses is just like handling events!

    #Handling responses

    In a JavaScript app, handling a HTTP response is a lot like handling any other event. As with mouse clicks or form submissions:

    • When an event occurs (i.e. a response is received), a callback will be called.
    • The callback will be passed an object holding the response’s details.
    • The callback can call a component’s setState() method to store the received data.

    In fact, there’s only one big difference: fetches can fail. You’ll need to handle the errors, so unlike UI events, you should always provide two callbacks — one to be called on success, and one to be called on failure.

    let promise = fetch('https://frontarm.com/example')
    
    promise.then(
      response => {
        this.setState({
          response: response,
        })
      },
      error => {
        this.setState({
          error: error,
        })
      }
    )

    The above example performs a HTTP request using the browser’s fetch() function. But the fetch() API has a number of intricacies that are out of scope of this course. So instead, I’ll provide you with something better!

    My fake API’s getRecords() function returns a JavaScript Promise, just as fetch() does. The promise resolves to an array of billionaires that you can use for your ContactList component. But it also fails 50% of the time, so you can test your error handling too.

    For example, you could use getRecords() to log a record (or an error message) to the console each time the user clicks a “Refresh” button.

    index.js
    api.js
    styles.css
    index.html
    import React from 'react'
    import ReactDOM from 'react-dom'
    import { getRecords } from './api'
    
    class App extends React.Component {
      constructor(props) {
        super(props)
        this.refresh = this.refresh.bind(this)
      }
    
      render() {
        return (
          <button onClick={this.refresh}>
            Refresh
          </button>
        )
      }
    
      refresh() {
        getRecords().then(
          (response) => {
            console.log('first stored record', response.data[0])
          },
          (error) => {
            console.log('failed with status', error.status)
          }
        )    
      }
    }
    
    ReactDOM.render(
      React.createElement(App),
      document.getElementById('root')
    )
    Build In Progress

      #Let’s fetch some data!

      Now that you can easily list your billionaire friends with getRecords(), it’s time for an exercise!

      Your task is to store the received array of billionaires to component state, and use this to render a list of billionaires or an error message.

      index.js
      Contacts.js
      api.js
      styles.css
      index.html
      import React from 'react'
      import ReactDOM from 'react-dom'
      import { getRecords } from './api'
      import { Contact, ContactForm, ContactList } from './Contacts'
      const { createElement } = React
      
      class App extends React.Component {
        constructor() {
          super()
          this.state = {
            contacts: [],
            contactsError: null,
          }
          this.refresh = this.refresh.bind(this)
        }
      
        render() {
          return (
            <ContactList onClickRefresh={this.refresh}>
              {this.state.contacts.map((contact, i) =>
                React.createElement(Contact, { ...contact, key: i })
              )}
              <ContactForm onAddContact={(contact) => {
                this.setState({
                  contacts: this.state.contacts.concat(contact)
                })
              }} />
            </ContactList>
          )
        }
      
        refresh() {
          alert('refresh!')
        }
      }
      
      ReactDOM.render(
        <App />,
        document.getElementById('root')
      )
      Build In Progress

      As usual, if you do get stuck and need to check the solution, make sure to get your own code working afterwards. And once you have, congratulations on building a raw React app with networking code! You’re now ready to take on a real project!

      But before celebrating, there’s still one tiny problem. Requiring the user to click a button to download the initial data is not ideal. The app really should be fetching the data by itself. But how?

      One solution would be to call getRecords() in the constructor. But while this would usually work, there are a few edge cases that you can run into. And besides, network calls in the constructor is just… nasty. So let’s use a lifecycle method instead!

      Progress to next section.