Request
A request, or an HTTP request, is an action that is sent to a server in order to get something, or to send some information. This includes the URL of the server, the headers and the body of the request.
Most of what will be explained will be important for requesting some information, but can also be applied when sending information as well.
Loading
Displaying a loading information for your users is an important step of a request because we never know what could happen on the network, maybe the connection is slow, maybe the server is slowed down because of the numerous requests.
Showing a loader, or a text indicating that the request is still being made is an additional step that can make your application look more professional and is more user friendly than thinking that everyone has a fast internet connection.
You can simulate slowed down request from the Developer Console in your favorite browser such as Firefox or Google Chrome.
Error
Things happen in the network, and we can't control everything beside what is happening inside our code.
The network might be shut down momentarily, or the user has activated airplane mode, or the server has been down for some time. We never know what kind of problem there might be, or when it can happen but we know that there might be some problem and we must account for that.
It is a good practice to account for these thing in the code, especially in JavaScript since sending a request often involves using a Promise, and a promise might be in a rejected state.
You can also simulate an offline connection in your browser from the Developer Console.
Cancelable
If you plan on giving your users data from a remote API, at least provide a way of canceling these requests.
This is a good practice and an added user experience bit in any application since getting a huge payload from a remote server might be costy for users that are on data plans and having the choice is a good way of showing your users that you are considering everyone, even those that cannot afford much data transfer.
Using JavaScript and the Web API Fetch, you can use a signal along with an Abort Controller in order to provide a way of canceling a request.
Validation
Finally, you have sent a request, everything goes according to plan and you receive a successful response. Or is it?
How can you be sure that the server won't change its response in a day, or a week, or a year? Your application might work for a while, but if anyone decide to send an object with a property instead of an array as usual, you might get into trouble because you'll try to iterate over an object instead of an array which is not possible out-of-the-box in JavaScript.
Data validation is an important step, and might as well be mandatory in some case because even if you know what you are doing today and you are the only developer for a frontend & backend application, you might not be alone in a year and people might join the fight and help you.
If you go back from a long vacation and the API has change, at least with data validation you know that this is a case you accounted for and your application won't crash suddenly (and you might even get better errors that will lead you to resolve this error quicker than without data validation).
Also, with data validation, you can rely on languages that are strongly typed like TypeScript to ensure that once this data has been parsed and validated, you are 100% sure you can iterate over it instead of being afraid it might change in a near future.
Example
Here is what a beginner application might look like in React for the example.
import React, { useEffect, useState } from "react";
export const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users").then(response => {
return response.json();
}).then(newUsers => {
setUsers(newUsers);
});
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.username}</li>
))}
</ul>
);
};
As you can see, no loading state, no cancelable request, no accounting for errors nor data validation.
Here is what it might look like with all these things added.
import React, { Fragment, useRef, useEffect, useState, useCallback } from "react";
const isValidUser = input => {
return typeof input === "object"
&& input !== null
&& typeof input.id === "number"
&& typeof input.username === "string";
}
const isValidUsers = users => {
if (!Array.isArray(users)) {
return false;
}
if (!users.every(user => isValidUser(user))) {
return false;
}
return true;
}
export const App = () => {
const [users, setUsers] = useState([]);
const abortController = useRef(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const cancel = useCallback(() => {
abortController?.current?.abort();
}, [abortController]);
useEffect(() => {
abortController.current = new AbortController();
const { signal } = abortController.current;
setError(null);
setLoading(true);
fetch("https://jsonplaceholder.typicode.com/users", {
signal
}).then(response => {
if (response.ok) {
return response.json();
}
return Promise.reject(new Error("Something went wrong"));
}).then(newUsers => {
if (!isValidUsers(newUsers)) {
throw new Error("Wrong response from the server");
}
setUsers(newUsers);
}).catch(error => {
setError(error);
}).finally(() => {
setLoading(false);
});
}, []);
return (
<Fragment>
{loading && (
<small>Loading, please wait...</small>
)}
{error && (
<small>{error.message}</small>
)}
<button onClick={cancel}>Cancel</button>
<ul>
{users.map(user => (
<li key={user.id}>{user.username}</li>
))}
</ul>
</Fragment>
);
};
Of course no styling has been applied so it looks awful but at least you get the idea.
We added a way of canceling the request, a loading indication to reassure the user, a display of any error and a basic data validation mechanism in order to ensure the data we get is not corrupted or has been changed.
Conclusion
We saw that in order to build reliable applications, there were 5 steps that we must follow whenever we make a request to a server:
- Send a proper request
- Display a loading state
- Display errors if any
- Make the request cancelable
- Validate the data we receive
If you manage to follow these steps, you'll build highly reliable applications that are time-tested and sturdy.
This will instantly make your application way better in the eyes of your users.
These concepts are not tied to JavaScript nor React and can be applied to pretty much any language or any framework & library out there as long as you follow these steps.
Top comments (9)
You should change this line
to
because your initial example causes a re-render each time you update the abort controller, and there's no apparent reason why that's neccessary.
Hi Joshua and thank you for your comment.
You are absolutely correct, I'll update the exemple to reflect the change.
Thank you for your contribution!
Great article! I never thought about making the request cancelable and validating the data received, but you make an excellent point on why we should do both.
Hi Alfonsina and thank you for your comment.
Glad you find this article useful. I wish you to make wonderful applications with all those tips!
I would change the isValidUsers function to:
user => isValidUser(user)
checks if a user is valid, so doesisValidUser
, so it's unneeded.And I would change the fetch to this:
This flows much better and doesn't need to be in a
.then
.Hi coolCucumber-cat and thank you for your answer!
You are very correct, I could have used the function
isValidUser
as a higher-order function and pass it directly to theArray.prototype.every
method.For this time, I'm not going to update my article with your changes as I want the readers that are interested in these tips to find them out in the comments.
In a real-world situation where I know my colleagues are comfortable with higher-order functions and functional programming in general (and I cannot install a third-party library such as Zod) I would totally go for it and I'm 100% agreeing with you!
Thank you for your contribution!
isValidUsers can be simplified to:
const isValidUsers = users => Array.isArray(users) && users.every(isValidUser);
Great article.
Thank you @Windya, glad you liked it.
I hope it serves you well in your future work!