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.
-
Functionality
Writing the code of what the side effect actually does
-
Dependencies, i.e. when to run
Defining how often the side effect needs to be performed
-
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!