March 3, 2018

/

React Crypto Stock Compare – Pt. 5: Using state to manage fetching data from an API

React Crypto Stock Compare – Pt. 5: Using state to manage fetching data from an API

This is to part 5 of a multi-part React tutorial that will teach you the basics of React while building a simple app to compare crypto-currency prices to stock prices. You can find the code to work along here and the starting point for this step can be found in the branch using-props by running git checkout using-props

So far I’ve been using getEthereumData and getMicrosoftData methods to pull in some JSON data that I downloaded from the Alpha Vantage API. So right now the data is not live its just the data from the day I created this example. In this step we are going to change this and show you how to get data from an API and use in your react app.

Fetch API

Fetch API is browser native interface that you can use to fetch resources, such as data from an API. It’s supported in all major browsers and Create React App has a polyfill to make it work in browsers that don’t.

You do not have to use fetch but I prefer to use it over helper libraries like axios and others because I would rather use native APIs where possible. But you don’t have to and can fetch data however you like in React.

React component state

The missing piece for React that I have not talked about is state. State is how you build interactions into your application. State is how you bring React’s one-way data binding, where props go down from the top as components render, into an interactive app.

Each component has its own local state that you can find on this.state. When either the props or state of a component change, it will trigger it to run its render method again. Functional components don’t have state so if you need state in your component you must use a class component.

So to summarize this idea:
– Need your application to react to events in the UI? Use state.
– Need to store some data that changes over time?
– Use state. Need to make an API request and respond when the data is returned? Use state.

There are endless applications for state but you also want to make sure that the right component owns the state. Enough theoretical, lets see how this works.

Coding Along

If you haven’t been following along and would like to you can clone the repository and get the starting code for this step by running the following from a command line:

git clone https://github.com/sethreidnz/crypto-stock-compare
cd crypto-stock-compare
git checkout using-props

Async, Await

I have some functions that pull data directly from the API in the src/api/index.js file. Here is an example:

export const getMicrosoftDataFromApi = async () => {
  const response = await fetch(
    `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=compact&apikey=${alphaVantageKey}`
  );
  const data = await response.json();
  return parseTimeSeriesData(data, 5);
};

Before I go into how to use these functions in our app let me explain this new syntax async and await. Once upon a time everything asynchronous in JavaScript was done via callbacks so you would have code like this:

function getDataFromApi(callback) {
  // get the data somehow
  callback(error, data)
}

Then you would call it like this and pass a “callback” function to it:

getDataFromApi(function(error, data){
 if(error){
   // do something with the error
 }
 // do something with the data
})

This resulted in what is referred to as “callback hell” where you have callbacks inside of callbacks inside of callbacks. This all changed with promises which made it possible to write code like this:

function getDataFromApi() {
  // get the data somehow and return a new Promise();
}
``

``` lang-javascript
getDataFromApi()
.then(function(data){
 // do something with the data
})
.catch(function(error){
// catch errors
})

Which might look quite similar but it allows you to chain a number of promises together with multiple then()‘s and not nest your callbacks over and over. It also lets you catch all errors in one catch block instead of having to handle errors in each and every callback. The fetch method used in the above getMicrosoftDataFromApi returns a promise and you could just use .then() to take action when the fetching is complete.

But now we have async functions which are part of the JavaScript spec and supported by all the major browsers, and Create React App has polyfills for older browsers.

Async function make it possible to treat functions returning Promises as if they were synchronous. Instead of of using .then() and .catch() you can write code that will force await the return of your the of a promise like so:

async function getDataFromApi() {
  // get the data somehow and return a Promise;
}

Then call it like this

try {
 const data = await getDataFromApi();
 // do something with the data
} catch(error) {
  // do something with the error
}

The main thing about this is that you no longer have to reason about asynchronous operations differently from regular synchronous ones. The above code reads just like it were a synchronous operation. The only thing is that you have to remember is to mark the function with the async keyword if you are going to use the await keyword within it.

Now that I’ve explained this have a look at my call to the Alpha Vantage API and see how I’ve used the keyword async in my method definition, and then I use the keyword await to wait for the responses from fetch and from response.json():

export const getMicrosoftDataFromApi = async () => {
  const response = await fetch(
    `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=compact&apikey=${alphaVantageKey}`
  );
  const data = await response.json();
  return parseTimeSeriesData(data, 5);
};

Since fetch returns a Promise I can await it and get the response. The response from fetch has a number of different properties and methods but the one we care about here is json(). This also returns a promise so we can await it and get back the data from the call to Alpha Vantage. Finally I run my data through a parsing function and we are good to go.

Fetching data and putting it in state

If you open up src/App.js we can now consume these async functions to get live data in our app. This requires introducing the most well used React life-cycle method componentDidMount. According to the docs:

componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

Sounds like just what we need! If you’re confused by the jargon “mounted” this just means when the component is first rendered. So if you think back to Part 1 where we used ReactDom.render() to load the <App /> component into our index.html file. That causes it to be mounted. When a sub-component of <App /> is rendered by <App /> that causes that component to be mounted and so on, all the way down your “component tree”.

Setting up your state

The first thing you need to do is setup the initial state which you can do in a couple of ways.

Inside the constructor:

class App extends Component {
  constructor() {
    super();
    this.state = {
      ethereumData: null,
      microsoftData: null,
      hasLoaded: false
    }
  }
  {// rest of <App />}
}

Note: You must call super() first to call the constructor of the React.Component parent class

Or as a class property:

class App extends Component {
  state = {
    ethereumData: null,
    microsoftData: null,
    hasLoaded: false
  }
  {// rest of <App />}
}

I prefer the second method if I don’t need to do anything else in my constructor but it doesn’t really matter. What I’m doing here is setting the property state to what I want it to look like the first time my component loads. In this case I am giving it two properties, one for each of the data arrays, ethereumData and microsoftData, and a flag hasLoaded so that I can display a spinner while I’m waiting for the data to load.

Updating your state

The next thing we need to do is call the API method to get the data, then update the state when it is returned. So we use the componentDidMount method described above:

class App extends Component {
  state = {
    ethereumData: null,
    microsoftData: null,
    hasLoaded: false
  }
  async componentDidMount() {
    const ethereumData = await getEthereumDataFromApi();
    const microsoftData = await getMicrosoftDataFromApi();
    this.setState({
      ethereumData,
      microsoftData,
      hasLoaded: true
    })
  }
  {// rest of <App />}
}

See how we have to use the setState method. This is because we need React to know that we have updated the state, so that a re-render is triggered. If you updated this.state directly this won’t happen.

Another thing to be very aware of is that setState is an asynchronous operation and React will batch the updates for performance reasons. Don’t rely on the value this.state being different on the very next line. It also does a good job of merging your state update with the current state. So you don’t have to include all the properties in the state to do an update, you can just update the properties you want to change and the rest will stay the same.

In this method I am updating the ethereumData and microsoftData with the data coming back from the API, and setting hasLoaded to true.

Using state in render()

The final thing we need to do is to use this state and the change in state in our render method:

render() {
  const { ethereumData, microsoftData, hasLoaded } = this.state;
  if (!hasLoaded) return <Loader/>
  return (
    <div className="crypto-stock-compare">
      <h1>Crypto Stock Compare</h1>
      <section className="value-table">
        <h2>Ethereum</h2>
        <PriceTable priceData={ethereumData} />
      </section>
      <section className="value-table">
        <h2>Microsoft</h2>
        <PriceTable priceData={microsoftData} />
      </section>
    </div>
  );
}

To break this down:

  • First I extract the pieces from the state I need
  • Then if the hasLoaded is false then I show my component
  • Otherwise I just let it render.

This is a very common pattern you can use to load data, or prevent a component rendering until some condition is met.

Final Component

I’ve explained the three steps we needed to do:

  • Add initial state
  • Update the state
  • Use the state in the render method

    Try replacing your App.js file with the following:

import React, { Component } from "react";
import "./App.css";
import { getEthereumDataFromApi, getMicrosoftDataFromApi } from "./api/index";
import { PriceTable } from "./components/PriceTable";
import { Loader } from "./components/Loader";

class App extends Component {
  state = {
    ethereumData: null,
    microsoftData: null,
    hasLoaded: false
  };
  async componentDidMount() {
    const ethereumData = await getEthereumDataFromApi();
    const microsoftData = await getMicrosoftDataFromApi();
    this.setState({
      ethereumData,
      microsoftData,
      hasLoaded: true
    });
  }
  render() {
    const { ethereumData, microsoftData, hasLoaded } = this.state;
    if (!hasLoaded) return <Loader />;
    return (
      <div className="crypto-stock-compare">
        <h1>Crypto Stock Compare</h1>
        <section className="value-table">
          <h2>Ethereum</h2>
          <PriceTable priceData={ethereumData} />
        </section>
        <section className="value-table">
          <h2>Microsoft</h2>
          <PriceTable priceData={microsoftData} />
        </section>
      </div>
    );
  }
}

export default App;

Run it up in the browser by running npm start in your terminal or command window and see what happens! You should see a loader in the top right of the screen while you wait for the data to come back and then the table shows up.

If you got lost or want to make sure you did it right you can have a look in the branch in Github or run:

git checkout using-state-for-api-requests

That’s it for this tutorial next up is the final step which I’m going to show you how to do “routing” with React Router v4 in Pt. 6: Creating a new pages with React Router

Next Step

Tags:

Seth Reid

/

React Crypto Stock Compare – Pt. 5: Using state to manage fetching data from an API