All Articles

Introducing HTTP service calls with Axios and the useEffect hook

Https
Communicating with the internet is pretty much always needed

In the previous post, we added some state into the application to change the way the information was displayed. The data itself was still hard-coded however, which does not provide a very realistic scenario.

In this article, I will cover how the frontend application can make calls to the backend, using the axios library and the useEffect React hook.

UseEffect Hook

Intro

The useEffect hook is another, default hook in the React library. In essence, the useEffect hook is your go-to whenever you want to introduce side effects into function components.

Just as with the useState, useEffect is available in React since version 16.8. They allow you to use state, perform side effects, and other features without writing specific new classes.

As mentioned, it should be used to perform side effects. Some potential side effects are:

  • Data fetching, e.g. from a backend
  • Setting up subscriptions
  • Changing the DOM manually
  • Setting timers

Setup

The useEffect hook is composed of three different parts.

  1. Functionality

    Writing the code of what the side effect actually does

  2. Dependencies, i.e. when to run

    Defining how often the side effect needs to be performed

  3. Cleanup upon component destruction

    Actions to be performed when the component is removed from the DOM

import * as React from "react";

useEffect(() => {
   function performAction(){}     // defining the action of the hook
   return function cleanup(){}    // defining the cleanup (optional)
}, [dep1, dep2])                  // defining the dependency array

// example
useEffect(() => {
   document.title = "Hello";     // Sets the document title to Hello when the component is rendered
   return function cleanup(){ document.title = "Bye bye"}    // When the component is destroyed, the title changes
}, [])                   // empty dependency array

Note about cleanup

The return in the useEffect is optional. Not every side effect requires a cleanup. In fact, they are usually not defined. However, if you have a subscription for example, it may be best to cancel that subscription when it is no longer needed, in order to prevent a memory leak.

Note about the dependencies

The dependency array is also not required, but it is very important to be aware of its role. If it is not defined, then the useEffect will run on every render. Thus, if your side effect is an http call, and you do not have a dependency array defined, you will make a lot of service calls, which might make that component unusable.

On the other extreme, having an empty dependency array [] will ensure that the useEffect will run only once, no matter what else happens inside the component.

As for when there are entries in the array, they ensure that the function of the useEffect will run every time the values of these entries change.

Usage

You are free to use as many useEffect hooks as you wish inside every component. This is especially useful in comparison to the previous way with Class Components, as there you would require more logic to separate the different side-effects, especially for when they’d need to run. Now, you can simply make one hook that runs once, another that runs upon every render, and some more that have a couple of dependencies.

UseEffect in action

Now that the theory is out of the way, we need to think about what side-effects we’d need in our displaying of accounts. Well, a fairly obvious starting place is to remove the hardcoded accounts that we currently have in the App.tsx (it would have made more sense in the AccountDisplay anyway… :) ).

That being said, I will not yet remove them altogether just yet, as we haven’t yet integrated backends. But the useEffect will already go fetch them instead of just having them, just as if it came from an actual backend.

In order to do this, we will create an AccountService.ts, so as far as the AccountDisplay is concerned, it’s already like a backend call.

class AccountService {
  static getAccounts = () => {
    const accounts: Array<Account> = [{
      id: 1,
      amount: 320.8,
      currency: "CHF",
      name: "myNiceAccount",
      description: "Wow, this is advancing!"
    }, {
      id: 2,
      amount: 94.6,
      currency: "EUR",
      name: "eurozz",
    }, {
      id: 3,
      amount: 420,
      currency: "USD",
      name: "Dollar account",
      description: "The account of dollars I use for stock investments"
    }, {
      id: 4,
      amount: 2600,
      currency: "CHF",
      name: "Savings",
      description: "Savings account. Do NOT touch!",
    }, {
      id: 5,
      amount: 5,
      currency: "NOK",
      name: "holiday money",
    }];

    return accounts;
  }
}

export default AccountService;

Now the AccountDisplay will need to fetch that data, and store it in its state.

function AccountDisplay() {
  const [view, setView] = useState("tiled");
  const [accounts, setAccounts] = useState<Array<Account>>([]);

  //...

  useEffect(() => {
    setAccounts(AccountService.getAccounts());
  }, []);
  
  //...

That’s all. Everything still looks the same, but the setup is done for the data coming from outside systems.

Axios

So now we need to hook up a backend, since we are still using the hard-coded response. There are several libraries out there that do this, but one of the most popular ones to use is Axios.

Axios provides an API that handles the service requests as promises. Overall, the API is very comfortable to use. Some great features include:

  • Parses response to JSON
  • Provides interceptors for both requests and responses
  • Error handling, with 4XX and 5XX responses being handled as errors
  • Easy overriding of timeouts and other configurations

Getting started

Installing axios is done with npm install axios.

Now some changes are needed in the AccountService. First of, we can finally remove the hard-coded list of accounts. Ahh, that feels good!

Next, we need to replace the returning of the list with the service call. Here, we require a simple GET request. We also need to think about the error handling, which is fortunately very easy to do with axios. For now, in that case, we will simply have an alert that notifies us about what went wrong. In the future, this can be replaced with a prettier error message.

I will keep the actual service call inside the AccountService. I do this mainly because I’m a fan of separation of concern - the representational components worry about displays, the services contain the service requests, and could be called from any component.

static getAccounts = async () => {
    return await axios.get(${url})
      .then((response: AxiosResponse<Array<Account>>) => {
        return response.data
      })
      .catch(() => {
        alert("Unable to retrieve the accounts.");
        return [];
      });
  }

To consume the changed signature of the function, the useEffect should be adapted:

  useEffect(() => {
    AccountService.getAccounts().then((accounts: Array<Account>) => setAccounts(accounts));
  }, []);

No catch is defined at this stage, as this is already covered in the service itself. However, if you wish to perform some additional operations that are specific to the component, you are of course free to do so. After all, you are still handling a Promise at this point.

Hooking up a backend

As you may notice, you now get an alert. This is the case because we haven’t yet defined the URL in the GET. Since we actually haven’t yet got started with the backend, we will use a free backend response generator. An example for such services is mocky, where you can simply paste the text that you want returned. Keep in mind that this needs to be valid JSON, otherwise axios will convert it to a String. If you are coding along, the response we’d like to have is:

[{
	"id": 1,
	"amount": 320.8,
	"currency": "CHF",
	"name": "myNiceAccount",
	"description": "Wow, this is advancing!"
}, {
	"id": 2,
	"amount": 94.6,
	"currency": "EUR",
	"name": "eurozz"
}, {
	"id": 3,
	"amount": 420,
	"currency": "USD",
	"name": "Dollar account",
	"description": "The account of dollars I use for stock investments"
}, {
	"id": 4,
	"amount": 2600,
	"currency": "CHF",
	"name": "Savings",
	"description": "Savings account. Do NOT touch!"
}, {
	"id": 5,
	"amount": 5,
	"currency": "NOK",
	"name": "holiday money"
}]

I have pasted the response I wanted, and have been given the URL https://run.mocky.io/v3/1a0f21ef-f96b-4b94-92a4-7afea097e0fb to call for that exact response. Keep in mind that this URL may be removed in the future, so it may not be available anymore when you try it also.

There we go, now the tiles (or the table) is displayed correctly again, with your information coming from the backend!

Configuring some axios properties

If you do not specify the axios properties explicitly, most will be just the browser defaults. Often, this is fine, however, in some cases, this may not be ideal. For example, the default timeout for Google Chrome is 300 seconds, which is waaaay too long!

Here is a default configuration for the axios instance I think makes sense for the app, for now. If we realize that some defaults are set a bit low, they can be changed in the future.

import axios from "axios";
import * as http from "http";
import * as https from "https";

const axiosInstance = axios.create({
  //keepAlive pools and reuses TCP connections, so it's faster
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  //follow up to 10 HTTP 3xx redirects
  maxRedirects: 10,

  //cap the maximum content length we'll accept to 50MBs, just in case
  maxContentLength: 50 * 1000 * 1000
})

// wait ao most 3s for a response
axiosInstance.defaults.timeout = 3000;

export default axiosInstance;

Now, don’t forget to replace the axios in the AccountService with this instance. If you want to test that this is actually working, just set the timeout to some ridiculously low value, like 1ms. If you do not see the alert at that stage, that means that the component is still using a new instance.

Okay, now everything is done! We have successfully set up a mock backend, on the real internet, have created an axios instance that queries that backend, and of which the result is inserted into the application with a useEffect hook.

The app is really advancing, and quite a few principles have already been covered!