Data Fetching In React
https://blog.logrocket.com/comprehensive-guide-data-fetching-react/
*Editor’s note:* This React data fetching tutorial was updated on 9 Aug, 2022 to include sections on what data fetching is, how to display fetched data, how to fetch data through caching libraries, and to update outdated links and code demos.
Modern applications handle lots of data. React in particular is great at displaying your data in a hierarchical component view, but how do your components get the data? There are many ways to go about it, each with its own pros and cons.
In this tutorial, we’ll focus on data fetching in React. We’ll demonstrate how to fetch data in React, complete with code examples, and introduce a handful of approaches to help you determine the best data fetching method for your React project.
We’ll cover the following in detail:
Example using the Fetch API in React
How to fetch data with Axios
What is data fetching in React?
A modern full-stack app consists of two major modules: frontend and backend. We typically display interactive UI elements through the app’s frontends built with React. On the other side, the backend persists and retrieves data in a remote server computer.
Because of this, we often need to fetch data from the backend to display it in the frontend. We can do this through various data transfer mechanisms, with the most popular mechanisms typically being RESTful interfaces, WebSockets, GraphQL interfaces, XML messages, and gRPC implementations.
Nowadays, the RESTful approach is undoubtedly one of the most popular data transfer mechanisms for React apps. We’ll focus mainly on RESTful-based data fetching in this tutorial.
The React RESTful data fetching flow is simple. In a component, we typically call to ask a networking client to fetch data by mentioning the RESTful endpoint and letting the browser perform an HTTP call. Once the browser receives data from the RESTful server for the particular endpoint, we can transform this data into React components or HTML elements and display them to the user.
Let’s understand data fetching with a practical React app!
Setting up an example data fetching app
To show how to fetch data in React, we’ll build a simple React application with components that fetch users from JSONPlaceholder. All of the components render the same data and look the same. I used the traditional <table>
HTML element for displaying tabular data to keep the tutorial simple and focus solely on data fetching. You can use any preferred data table component in your React apps.
Here’s what our example app looks like:
First, download or clone the predeveloped sample app from GitHub. Install dependencies with the following command:
npm install
# --- or ---
yarn install
We don’t need to set up any local web API since we use the remote JSONPlaceholder service as the data source. Run the React app as usual:
npm start
# --- or ---
yarn start
You will now see the datatables as shown in the preview screenshot.
Before diving into specific details about data fetching implementations, let’s study the main App
component structure. The main App
component is simply a functional component. It renders the various data patterns components that illustrate each method of data fetching:
import React from 'react';
import UserTableAutonomous from './components/UserTableAutonomous';
import UserTableHOC from './components/UserTableHOC';
import UserTableReactHooks from './components/UserTableReactHooks';
import UserTableRenderProps from './components/UserTableRenderProps';
import SimpleUserTable from './components/SimpleUserTable';
import './App.css';
function App() {
return (
<div className='App'>
<h2> User Table - Autonomous</h2>
<UserTableAutonomous/>
<h2> User Table - Higher Order Component</h2>
<UserTableHOC/>
<h2> User Table - Render Props</h2>
<UserTableRenderProps children={SimpleUserTable}/>
<h2> User Table - React Hooks</h2>
<UserTableReactHooks/>
</div>
);
}
export default App;
Without further ado, let’s get started with data fetching in React.
Overview of how to fetch data in React
If you’re just starting out with React, you may have only worked on simple, beginner-level projects that don’t need to access or handle data. As you go along your React journey and learn how to build more complex projects, your apps will almost certainly require this functionality. In fact, data fetching is a core requirement of almost every React app.
There is a variety of ways to fetch data in React, including using the inbuilt Fetch API, Axios, and more. We’ll go over all these methods in detail. You can also fetch data in higher-order components and render props, from a GraphQL backend, and more. Keep reading to learn how.
Fetching server-provided data
This is the old-school way of getting data for your app. The data is embedded in the HTML sent from the server. If you want fresh data, you need to refresh the page manually or have the page refresh periodically. Remember this?
<meta http-equiv="refresh" content="30">
It’s not particularly relevant for a React application, which has much more dynamic, fine-grained ways to update itself, but it is still a legitimate way to get data from the server to the browser. A lot of legacy web applications still use it, and if JavaScript is disabled or you must deal with ancient browsers, it may even be the best approach because is very simple and straightforward.
How React components fetch data
React components can just fetch their own data. The big question is when to fetch the data. There are several options:
- Start with no data and fetch data based on user actions like clicking a button
- Load the data once
- Load the data periodically
Since the component is totally autonomous, no other component can tell it that it’s time to load its data. In this case, I chose to load the data for the first time in componentDidMount()
and also set a timer that will fetch the data again every five seconds.
Let’s look at the UserTableAutonmous
component and dissect it piece by piece. It’s a standard class-based React component. Its state includes two fields: a boolean isFetching
initialized to false
since it’s not fetching yet, and an empty list of users (the data it wants to fetch).
class UserTableAutonomous extends Component {
constructor(props) {
super(props);
this.state = {
isFetching: false,
users: []
};
}
First, the render()
method renders an HTML <table>
element. It then shows data records within the table by transforming the users
array to <tr>
elements with the map
function. If it’s in the middle of fetching, a “Fetching users…” message is displayed, too.
This is super-rudimentary progress reporting, so you could consider adding professional CSS loaders/spinners based on your app’s UI/UX principles. The HTML table element will display only the id, name, and username fields of each user, though there are several other fields.
render() {
return (
<div>
<table>
<tbody>
{this.state.users.map((user, index) => (
<tr key={index} className={rowClassNameFormat(index)}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.username}</td>
</tr>
))}
</tbody>
</table>
<p>{this.state.isFetching ? 'Fetching users...' : ''}</p>
</div>
)
}
As I discussed before, the actual data fetching happens in componentDidMount()
, the React lifecycle method being called when the component is mounted and ready to go. Some people may argue that it’s better to use componentWillMount()
which gets called when the component is about to be mounted and starts data fetching earlier to save time. There are two important reasons against it, however.
First, it’s deprecated as of React 17. Second, when you use the Fetch API or Axios in componentWillMount()
, React will render without waiting for it to finish and will cause an empty render for the first time — so you don’t really save any time.
Note that componentDidMount()
is called after the first render, so you still need to handle the first empty render. In our demo, I use the “Fetching users…” message.
Another option is to do your initial data fetching in the constructor, but that will delay the first render of your component.
OK, it’s settled — we’ll fetch our data in componentDidMount()
. The code simply calls the fetchUsers()
method and starts a timer that will call fetchUsers()
every five seconds.
componentDidMount() {
this.fetchUsers();
this.timer = setInterval(() => this.fetchUsers(), 5000);
}
The componentWillUnmount()
method is called when our component goes away, and it’s a good time to stop the timer by calling clearInterval()
and setting it to null.
componentWillUnmount() {
clearInterval(this.timer);
this.timer = null;
}
fetchUsers()
sets the isFetching
state variable to true
, so while fetching new data, the component renders the “Fetching users…” message within a <p>
tag after the datatable. Then it gets the users by some “magic” and the sets isFetching
back to false
.
async fetchUsers() {
try {
this.setState({...this.state, isFetching: true});
// fetch data ....
this.setState({users: response.data, isFetching: false});
} catch (e) {
// error handling
}
}
I’m not a big fan of autonomous components; they are too much of a black box. They mix two very different concerns of data fetching and data display and are more difficult to test.
There are multiple ways to implement the fetchUsers()
function. I’ve used three different implementations in different components. All three implementations accomplish the same task:
- The inbuilt Fetch API
- Axios
- Async/await with Axios
I could have likewise used async/await with the Fetch API. I arbitrarily used different implementations in different components; they are all exchangeable. The pros and cons are more ergonomic than functional.
Let’s take a closer look at these alternative implementations.
Example using the Fetch API in React
The Fetch API is a modern replacement for the legacy XMLHttpRequest
API. All modern browsers typically support the Fetch API nowadays, so we can use it for data fetching in React without adding another dependency to your package.json
.
I’ve used Fetch in the UserTableHOC
component. I actually called the function fetchUsersWithFetchAPI()
but assigned it to a variable called fetchUsers
, so the component just calls fetchUsers()
.
The function starts by setting the isFetching
variable to true
and then calls fetch. Fetch returns a promise, which resolves to a response. The response’s json()
method returns a JavaScript object. It then sets the users in state and resets isFetching
to false
.
If something goes wrong, the catch handler logs the error to the console and, with the fetch finished, resets the isFetching
variable.
fetchUsersWithFetchAPI = () => {
this.setState({...this.state, isFetching: true});
fetch(USER_SERVICE_URL)
.then(response => response.json())
.then(result => {
this.setState({users: result, isFetching: false})
})
.catch(e => {
console.log(e);
this.setState({...this.state, isFetching: false});
});
};
fetchUsers = this.fetchUsersWithFetchAPI;
Calling the API using Fetch
We’ve called the JSONPlaceholder users API using the fetch
function before. The fetchUsersWithFetchAPI
triggered a GET
API request since we invoked fetch
with no specific HTTP method (i.e., PATCH
, POST
, etc.).
Calling a web API refers to making CRUD operations, not just R (read) operations. For example, if we need to insert a new user via a RESTful backend, we may need to call the API with a POST
HTTP request and a JSON payload containing new user data. Similarly, we often need to call the API with PATCH
and DELETE
HTTP methods as well.
We can use the fetch
function’s method
option to call the API with different HTTP methods. The following code snippets insert a new user:
const user = { username: 'John100' };
fetch(USER_SERVICE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
})
.then((response) => response.json())
.then((user) => {
console.log('New user:', user);
})
.catch((e) => {
console.error(e);
});
Here, we used USER_SERVICE_URL
all the time, but a real-world API may contain multiple endpoints (i.e., http://www.example.com/api/users
, http://www.example.com/api/orders
, etc.). You can then use several strategies to call Fetch API in a more readable way.
One approach is to use a constant for the base URL and concatenate it with fetch
calls:
const BASE_URL = 'https://www.example.com/api';
fetch(`${BASE_URL}/users`)
// ...
fetch(`${BASE_URL}/orders`)
// ...
We can make the above code even more readable by creating an interceptor for the fetch
function:
function fetchWithBase(fetch, baseURL) {
return (url, ...params) => {
if(url.startsWith('/'))
return fetch(baseURL + url, ...params)
else
return fetch(url, ...params);
}
}
const fetch = fetchWithBase(window.fetch, BASE_URL);
fetch('/users')
// ...
fetch('/orders')
// ...
The Fetch API is pretty verbose and cumbersome, but it is standard and has no external dependencies — that’s the selling point of the Fetch API. Then again, this is JavaScript; lots and lots of dependencies are the law of the land. Enter Axios.
How to fetch data with Axios
I’ve used Axios for the UserTableRenderProps
component. Axios also has a promise-based API similar to Fetch, but Axios saves the JSON parsing phase and handles all errors. The Fetch API, for example, returns 404 as a normal response, so you need to check the response in your code and throw an error yourself if needed.
fetchUsersWithAxios = () => {
this.setState({...this.state, isFetching: true});
axios.get(USER_SERVICE_URL)
.then(response => {
this.setState({data: response.data, isFetching: false})
})
.catch(e => {
console.log(e);
this.setState({...this.state, isFetching: false});
});
};
fetchUsers = this.fetchUsersWithAxios
The code is almost identical to the Fetch API version, with one less step, developer-friendly code and more robust error handling.
Is Fetch better than Axios?
You can communicate with servers through the HTTP protocol using either the Fetch API or Axios. What’s the difference?
The Fetch API provides a fetch()
method defined on the window object, as well as a JavaScript interface for accessing and manipulating HTTP requests and responses. fetch()
has only one mandatory argument: the URL of the resource to be fetched. It returns a promise that can be used to retrieve the response of the request.
Axios, on the other hand, is a JavaScript library that enables you to make HTTP requests from the browser and Node.js environment. It supports the Promise API in JavaScript ES6. Axios enables you to intercept HTTP requests and responses, protects the client side against cross-site request forgery (XSRF), and is capable of canceling requests.
So which React data fetching method is better: fetch
or Axios? It depends on the particular requirements of your project and your comfort level when it comes to using inbuilt APIs.
Axios provides an easy-to-use API in a compact package for most of your HTTP communication needs. However, if you prefer to stick with native APIs, there’s nothing stopping you from implementing Axios features. It’s certainly possible to reproduce the key features of the Axios library using the fetch()
method provided by web browsers.
Ultimately, whether it’s worth loading a client HTTP API depends on whether you’re comfortable working with inbuilt APIs.
How to fetch data with async/await in React
I’ve used the async/await syntax in the UserTableAutonomous
component. Those promise chains are a huge improvement over the old callback hell, but it can get much better. See how nice and natural the same code looks with async/await:
async fetchUsers() {
try {
this.setState({...this.state, isFetching: true});
const response = await axios.get(USER_SERVICE_URL);
this.setState({users: response.data, isFetching: false});
} catch (e) {
console.log(e);
this.setState({...this.state, isFetching: false});
}
}
This is my favorite variant without a doubt.
How to fetch data from a GraphQL API in React
The users API is a REST API. How about GraphQL backend? GraphQL servers typically return JSON over HTTP, too.
The main difference is that there is one query endpoint to fetch data (ignoring mutations and subscriptions), and the actual data requested and returned follows the GraphQL schema. The data fetching strategies and tactics don’t distinguish between REST and GraphQL, and they’ll work equally well on both.
Now, WebSockets versus gRPC is a different story — we’ll leave that for another day.
How to fetch data in higher-order components
Higher-order components are composite components wherein a top-level component is responsible for fetching the data and propagating it to child components. Higher-order components can be arbitrarily nested.
Several descendant components may receive different parts of the fetched data, while other components in the hierarchy may not use the data at all. Here’s a little diagram to illustrate this:
The basic idea is to isolate the concern of fetching and distributing the data from the concern of actually doing something with the data. In scenarios where multiple components need different aspects of the data, it is also more efficient because you only fetch the data once. Let’s see how it plays out.
The SimpleUserTable
component knows nothing about servers, lifecycle methods, data fetching, or error handling; all it does is receive the users list in its props and render them using the HTML <table>
element. It does understand the properties of a user object and expects an id, name and username.
import React from 'react';
import '../css/Table.css';
function rowClassNameFormat(index) {
return index % 2 === 0 ? 'Gold-Row' : 'Silver-Row';
}
const SimpleUserTable = (props) => {
return (
<div>
<table>
<tbody>
{props.data.map((user, index) => (
<tr key={index} className={rowClassNameFormat(index)}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.username}</td>
</tr>
))}
</tbody>
</table>
<p>{props.isFetching ? 'Fetching users...' : ''}</p>
</div>
)
};
export default SimpleUserTable;
It’s interesting that this knowledge of the user object is just a partial view. The actual user object returned from JSONPlaceholder has much more information:
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "\[Sincere@april.biz\](mailto:Sincere@april.biz)",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
}
The SimpleUserTable
cares only about the id, name, and username. If the backend server adds more info or removes/renames some unused fields, this is totally fine. To become more familiar with rendering data, you can try to update SimpleUserTable
for rendering concatenated address fields and company details. We’ll discuss how to render fetched data thoroughly in an upcoming section!
So what fetches the actual data? That would be the UserTableHOC
. It fetches the users in its componentDidMount
by calling the fetchUsers()
method that updates the users, and isFetching
is the state. The render()
method simply passes the state to the child SimpleUserTable
.
import React, { Component } from 'react';
import SimpleUserTable from './SimpleUserTable';
const USER_SERVICE_URL = 'https://jsonplaceholder.typicode.com/users';
class UserTableHOC extends Component {
constructor(props) {
super(props);
this.state = {
isFetching: false,
users: []
};
}
render = () => <SimpleUserTable data={this.state.users}
isFetching={this.state.isFetching}
/>;
componentDidMount() {
this.fetchUsers();
}
fetchUsersWithFetchAPI = () => {
this.setState({...this.state, isFetching: true});
fetch(USER_SERVICE_URL)
.then(response => response.json())
.then(result => {
this.setState({users: result, isFetching: false});
})
.catch(e => {
console.log(e);
this.setState({...this.state, isFetching: false});
});
};
fetchUsers = this.fetchUsersWithFetchAPI;
}
export default UserTableHOC;
In practice, we split the UserTableAutonomous
into two nested components; the code is pretty much identical, but it’s much cleaner. We’re all set in case we want to have multiple components that display user data in different ways.
For example, if we want to enable user selection and then displaying the full info of the selected user in another component (e.g., FullUserInfo
), the UserTableHOC
can just pass the relevant user info to the FullUserInfo
component.
That sounds great, but there is a lot of work in these cases, such as informing the HOC about selections in child components and passing fetched data through props of deeply nested component hierarchies.
So the HOC is not only responsible for fetching data, it is also responsible for rendering the components directly below it in the hierarchy and potentially responding to events originating from these children.
Our next data pattern addresses these concerns, but it comes with its own trade-offs.
How to fetch data in render props
What if we could implement a generic data fetcher that knows nothing about what is supposed to do something with the data? It turns out to be a common practice. The trick is to use a layer of indirection.
As the saying goes, “You can solve any problem in computer science with an additional layer of indirection … except for the problem of too many layers of indirection.”
The React pattern is often called render props. The idea is to pass a prop to a component, which is a function and not a static value or object. The receiving object will execute this prop, which is often used in the render()
method — hence the name render prop.
What that buys you is the ability to deeply customize the way the target component works by replacing parts of its logic with your function. If you’re familiar with object-oriented design patterns, it is similar to the strategy pattern or the template method pattern.
The code of UserTableRenderProps
is very similar to UserTableHOC
. The big difference is in the render()
method, which calls its props.children()
function. This increases the level of abstraction because the component doesn’t need to know anything about its children.
import React, { Component } from 'react';
import axios from 'axios';
const USER_SERVICE_URL = 'https://jsonplaceholder.typicode.com/users';
class UserTableRenderProps extends Component {
constructor(props) {
super(props);
this.state = {
isFetching: false,
data: []
};
}
render = () => this.props.children(this.state);
componentDidMount() {
this.fetchUsers();
}
fetchUsersWithAxios = () => {
this.setState({...this.state, isFetching: true});
axios.get(USER_SERVICE_URL)
.then(response => {
this.setState({data: response.data, isFetching: false});
})
.catch(e => {
console.log(e);
this.setState({...this.state, isFetching: false});
});
};
fetchUsers = this.fetchUsersWithAxios;
}
export default UserTableRenderProps;
That’s cool, but that means whatever passes the render props up top needs to know about the internal structure.
When does it make sense to use render props? A good example is in a deep hierarchy where the data fetching components can share a cache. In this case, it makes sense to have multiple data fetchers that have different children, as opposed to HOCs, where the children are fixed (hard-coded in the render()
method of the HOC component).
Let’s take another look at the App()
functional component from App.js that passes the children
render prop to the UserTableRenderProps
. As you can see, it needs to know about SimpleUserTable
and pass it along.
function App() {
return (
<div className='App'>
<h2> User Table - Autonomous</h2>
<UserTableAutonomous/>
<h2> User Table - Higher Order Component</h2>
<UserTableHOC/>
<h2> User Table - Render Props</h2>
<UserTableRenderProps children={SimpleUserTable}/>
<h2> User Table - React Hooks</h2>
<UserTableReactHooks/>
</div>
);
}
Fetching data with React Hooks
Data fetching in React used to require a class-based component with state and lifecycle methods. But React 16.8 brings us Hooks.
Patterns such as higher-order components and render props require you to restructure your component hierarchy and/or propagate a lot of state through your hierarchy (either directly with props or with various wrappers, providers, and consumers). In addition, people struggle with classes and the way they are implemented.
The idea of React Hooks is to break state management into independent functions that don’t require fitting the round peg of state into the square hole of class lifecycle methods.
All of React’s features can be used in functional components and don’t require a class. In particular, we can use React Hooks for data fetching.
Let’s examine the code of the UserTableReactHooks
functional component. First, the useState()
state Hook is called with an initial state. This is similar to the constructor. The Hook returns two values: the current state and a function to update it. Note that you can have multiple state Hooks, which could be useful if you need to update independently different parts of the state.
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import SimpleUserTable from './SimpleUserTable';
const USER_SERVICE_URL = 'https://jsonplaceholder.typicode.com/users';
function UserTableReactHooks() {
const [data, setData] = useState({users: [], isFetching: false});
So far, so good. To perform side effects like data fetching, we will use an effect Hook. Effect Hooks accept a function and run it after each render by default.
In this case, I want it to run just once, so I pass both a function and an empty array. The array argument tells the Hook to apply the effect (i.e., run the function) only if the state variables listed in the array are changed. Since I passed an empty array, there is no state variable to watch for and the effect will run just once.
useEffect(() => {
const fetchUsers = async () => {
try {
setData((data) => ({users: data.users, isFetching: true}));
const response = await axios.get(USER_SERVICE_URL);
setData({users: response.data, isFetching: false});
} catch (e) {
console.log(e);
setData((data) => ({users: data.users, isFetching: false}));
}
};
fetchUsers();
}, []);
You can think of effects as a combination of componentDidMount()
and componentDidUpdate()
of class-based components.
Finally, it just returns the SimpleUserTable
with the local state for rendering.
return <SimpleUserTable data={data.users}
isFetching={data.isFetching}
/>;
}
export default UserTableReactHooks;
Hooks are a cool and ergonomic addition to React. I highly recommend that you get familiar with them.
Fetching data through caching libraries
In previous examples, we used a simple generic flow to display data from a web service. First, we called the RESTful API with a networking client. Next, we stored data temporarily in a state variable. Finally, our React components rendered the fetched data with the curly braces syntax. This flow is simple and easy to implement, but it comes with several drawbacks:
- If we use a data fetching component several times, our React app will send the same network request multiple times
- We will have to define state variables for tracking the loading state and errors ourselves
- There is no caching layer – components will display the loading indicator initially whenever they get rendered or rerendered until the data fetching requests get completed.
We can implement a caching layer between data rendering and fetching to find a solution for the above issues. Caching libraries helps us fetch data more efficiently and in a more user friendlier way. React SWR and TanStack Query are popular caching libraries in the React ecosystem.
You can learn more about about caching libraries, React SWR, and TanStack Query here.
How to display fetched data
Earlier, we discussed fetching data with the Fetch API and Axios. We also passed fetched data to the SimpleUserTable
component for rendering. In this tutorial’s example app, we had to display an array of users. Similarly in your React apps, you may have to display an array of products, a single sales order information, an image from a URL, and other things of that nature.
React lets us render data dynamically with JSX expressions syntax (AKA the curly braces syntax).
Look at SimpleUserTable
. You can see how we dynamically display the loading indicator text:
<p>{props.isFetching ? 'Fetching users...' : ''}</p>
So how can we render a list of users in an HTML <table>
element? The <table>
element lets us add row data with <tr>
tags inside of a <tbody>
tag. We can convert the users
JavaScript array to a set of <tr>
elements with the curly braces syntax. We all know that we can use the map()
method to transform data, so it’s possible to render fetched users array with map()
as follows:
<tbody>
{props.data.map((user, index) => (
<tr key={user.id} className={rowClassNameFormat(index)}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.username}</td>
</tr>
))}
</tbody>
In React, we need to use a unique value with the key
prop for component lists, so here we used user.id
as the unique key. In this sample app, we styled table rows differently based on the row index with the rowClassNameFormat()
function.
Modern web apps typically offer pagination and sorting-like features with tabular data, so if we use TanStack Table (or a similar library), we can render modern fully-featured datatables with less code.
Concurrent Mode and React Suspense
If you’re a React developer, by now you’ve most likely heard of Concurrent Mode. Suspense is a mechanism within Concurrent Mode that enables your components to display something as a fallback while it waits for some long-running operation to finish. Obviously, data fetching is a long-running operation, and you may want to display something like a message, progress bar, or spinner while your data is being fetched.
In other words, React Suspense is a set of features that help React apps stay responsive regardless of a user’s device capabilities or network speed. It wraps your custom components and enables them to communicate to React that they’re waiting for some data to load before rendering the component.
Suspense is neither a data fetching library like react-async
nor a state management tool a-la Redux. It simply prevents your components from rendering to the DOM until some asynchronous operation (i.e., a network request) is completed.
Take the following example:
loading...
}> />
<span class="typ">Todos</span>
is wrapped with a <span class="typ">Suspense</span>
component that has a <span class="pln">fallback</span>
prop.
If Todos
is waiting for an asynchronous operation, such as retrieving the list of to-dos from an API, React renders <span class="tag"><p></span><span class="pln">loading…</span><span class="tag"></p></span>
to the DOM instead.
The <span class="typ">Todos</span>
component is rendered when the operation ends. You might be tempted to attempt the same with the following code:
...
if (loading) {
return <p>loading...p>
}
return <Todos />
...
This doesn’t quite work because it assumes the async operation was triggered by a parent component and that <span class="tag"><Todos</span><span class="pln"> </span><span class="tag">/></span>
is being rendered by this parent component after the operation is complete. But what if Todos
triggered the operation?
In this case, you would need to move the loading check from the parent component to the Todos
component. Let’s say, then, that there are more components, each triggering its own async request. Each child component would have to manage its own loading state independently, making it difficult to orchestrate your data loading operations in a clean way that doesn’t result in a janky UX.
loading...
}> />
/>
In the above example, we added another Tasks
component. Let’s assume this component also triggers its own async operation. By wrapping both components in Suspense
, you’re essentially telling React not to render either component until both operations are resolved. To do the same without Suspense, you would need to move the async calls to the parent component and add an if
check for the loading
flag before rendering the components.
Of course, you can mix and match approaches — including fetch-on-render, fetch-then-render, and render-as-you-fetch. You can learn more about these approaches by reading our comprehensive React Suspense tutorial.
If you already have some components that fetch data in a certain way, and other components that use another method, they can all live happily in the same application. But if you’re starting from scratch, using React Hooks and Suspense will likely be the best path forward.
It’s important to note that React Suspense is an experimental feature that is not yet available in a stable release.
Conclusion
We covered a lot of ground in this article. We explored all the common React data fetching strategies and tactics. We weighed the pros and cons of each approach and demonstrated each one in code.
At this point in time, I would go for React Hooks and Axios using async/await syntax. In the near future, it would be interesting to check out Suspense for data fetching.
Thanks for reading!
Making Asynchronous HTTP Requests in JavaScript with Axios
https://stackabuse.com/making-asynchronous-http-requests-in-javascript-with-axios/
Introduction
Axios is a Promised-based JavaScript library that is used to send HTTP requests. You can think of it as an alternative to JavaScript's native fetch()
function.
We will be using features such as Promises, async/await
, and other modern JavaScript design patterns in this tutorial. If you'd like to get up to speed or refresh your memory, you be interested in reading these articles before continuing:
- This article uses the arrow notation introduced in ES2015 to define functions. You can read more about it on Arrow Functions in JavaScript article.
- Axios is a Promised-based library. If you need to learn more about Promises, you can read our Promises in Node.js guide.
- To improve our experience with Promises, we'll use Node.js
async/await
syntax. You can read our Node.js Async Await in ES7 article to master this feature!
In this tutorial, we will make GET
, POST
, PUT
, and DELETE
requests to a REST API using Axios. Let's learn a bit more about this library.
What is Axios?
Axios is a modern, Promise-based HTTP client library. This means that Axios is used to send an HTTP request and handle their responses, all using JavaScript's promises. Axios supports both Node.js and JavaScript in the browser.
Axios is also free and open-source. You can visit its GitHub Repository to see its code and documentation.
It comes built-in with some web security by protecting users against attacks such as Cross-Site Request Forgery (CSRF).
As a result of its features and ease of use, it's become a popular choice for JavaScript developers to use when making HTTP calls. Let's get started by setting up Axios.
Setting up Axios
Let's first create a new folder and initialize NPM with the default settings:
$ mkdir axios-tutorial
$ cd axios-tutorial
$ npm init -y
Next, we can use NPM to install the library:
$ npm i --save axios
Note: If you're using TypeScript in your project (for example with an Angular app), the Axios library comes bundled with its types definitions. You don't have to take an extra step to install types!
If you are on the browser, you can use a CDN to import the script as well.
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
This tutorial uses Node.js with CommonJS to load our libraries. CommonJS is a standard for loading modules, in particular, it specifies the require()
keyword to do so. The examples should be working regardless of the platform without any changes.
Now that we've set up Axios in our development environment, let's head straight into making HTTP requests.
Writing Asynchronous Requests With Axios
In Node.js, input and output activities like network requests are done asynchronously. As Axios uses Promises to make network requests, callbacks are not an option when using this library. We interact with Axios using Promises, or the async/await
keywords which are an alternative syntax for using Promises.
Importing Axios
If you are using CommonJS, there are two methods in Node.js to import the library.
You can import the module in your code like this:
const axios = require('axios')
However, many IDE and code editors can offer better autocompletion when importing like this:
const axios = require('axios').default;
This works while using CommonJS to import modules. We recommend you use the second method as autocompletion and seeing code documentation in your IDE can make the development process easier.
With the library imported, we can start making HTTP requests.
Sending GET Requests
Let's send our first request with Axios! It will be a GET
request, typically used to retrieve data.
We will make an HTTP request to an external API that sends us a list of blog posts. Upon receiving the data, we'll log it's contents to the console. If we encounter an error, we'll log that too.
Let's see how to make one using the default Promise syntax. In a new file called getRequestPromise.js
, add the following code:
const axios = require('axios').default;
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(resp => {
console.log(resp.data);
})
.catch(err => {
// Handle Error Here
console.error(err);
});
To make a GET
request, we pass the URL of the resource as the argument in the axios.get()
method.
If you run this code with node getRequestPromise.js
, you would see the following output:
[ { userId: 1,
id: 1,
title:
'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body:
'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum
est autem sunt rem eveniet architecto' },
{ userId: 1,
id: 2,
title: 'qui est esse',
body:
'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro ve
l nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla' },
...
Now let's see how we can rewrite the same code this using async/await
keywords. In a new file getRequestAsyncAwait.js
, add the following code:
const axios = require('axios');
const sendGetRequest = async () => {
try {
const resp = await axios.get('https://jsonplaceholder.typicode.com/posts');
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
sendGetRequest();
To use the async/await
syntax, we need to wrap the axios.get()
function call within an async
function. We encase the method call with a try...catch
block so that we can capture any errors, similar to the catch()
method we used in the Promise version. The variable that received the HTTP data had to use the await
keyword to ensure the asynchronous data was received before continuing. From here on out, we'll only use the async/await
syntax in our examples.
Running this code will print the same output to the console as the original Promise example.
Free eBook: Git Essentials
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Download the eBook
An Axios response for an HTTP request (the resp
object in the example) will contain the following information about the HTTP response:
data
– The response body provided by the server. If the response from the server is a JSON, Axios will automatically parse data into a JavaScript object.status
– The HTTP status code from the response e.g.200
,400
,404
.statusText
– The HTTP status message from the server response e.g.OK
,Bad Request
,Not Found
.headers
– The HTTP headers accompanying the response.config
– The configuration that was provided to the Axios API for the request.request
– The native request that generated the response. In Node.js this will be aClientRequest
object. In the browser, this will be anXMLHTTPRequest
object.
Now that we've seen how to make a GET
request with Axios, let's look at how to make a POST
request.
Sending POST Requests
We send POST
requests to create a new resource in a REST API. In this case, we will make a POST
request with Axios to make a new blog post for a user.
Create a new file called postRequest.js
and enter the following code:
const axios = require('axios').default;
const newPost = {
userId: 1,
title: 'A new post',
body: 'This is the body of the new post'
};
const sendPostRequest = async () => {
try {
const resp = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost);
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
sendPostRequest();
To send a POST
with axios.post()
you must first supply the URL, and then provide the request data in the second argument. In this case, we are sending the data in the newPost
variable, which will be sent to our API as JSON.
Running this with node postRequest.js
produces the following successful result:
{ userId: 1,
title: 'A new post',
body: 'This is the body of the new post',
id: 101 }
Let's move on to see how we can send PUT
requests.
Sending PUT Requests
PUT
requests are used to replace data at an endpoint. You can use the axios.put()
method to send a PUT
request in a similar fashion to how we send POST
requests.
To see it in action, let's create a PUT
request that updates the properties of the first blog post. Create a new file called putRequest.js
with the code below:
const axios = require('axios').default;
const updatedPost = {
id: 1,
userId: 1,
title: 'A new title',
body: 'Update this post'
};
const sendPutRequest = async () => {
try {
const resp = await axios.put('https://jsonplaceholder.typicode.com/posts/1', updatedPost);
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
sendPutRequest();
Like with POST
, we provide the URL and the data we want to be uploaded. Running this with node putRequest.js
gives us:
{ id: 1, userId: 1, title: 'A new title', body: 'Update this post' }
Now that we've covered two ways to upload data, let's look at how we can remove data.
Sending DELETE Requests
You can send an HTTP DELETE
request using the axios.delete()
method to remove data from a RESTful API.
Let's remove a blog post by sending a DELETE
request with Axios. In a new file called deleteRequest.js
, enter the following:
const axios = require('axios').default;
const sendDeleteRequest = async () => {
try {
const resp = await axios.delete('https://jsonplaceholder.typicode.com/posts/1')
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
sendDeleteRequest();
The axios.delete()
function only needs the URL of the resource we want to remove. Executing this program with node putRequest.js
displays this in the terminal:
{}
This means no data was returned, which is fine when a resource is removed. However, as no error was thrown by Axios, we're pretty sure that it was processed correctly.
Let's have a look at an alternative way of sending Axios requests using configurations,
Configuring Requests
As an alternative to specifying the function to make the request, we can provide a JavaScript object that configures how Axios sends a request. For example, if I wanted to send a PUT
request without using axios.put()
, we can configure Axios like :
const axios = require('axios').default;
const sendRequest = async () => {
try {
const resp = await axios({
method: 'PUT',
url: 'https://jsonplaceholder.typicode.com/posts/1',
data: {
id: 1,
userId: 1,
title: 'A new title',
body: 'Update this post'
}
});
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
}
sendRequest();
In this case, we use axios
as a function directly. We pass it a JavaScript function that contains the HTTP method being used in the method
, the API endpoint in the url
and any data in the request in the data
property.
The end result is the same, so you can use this way of making requests if it appeals to you more.
Now that we have a handle on sending requests, let's modify them by setting custom headers.
Set Custom Headers
For certain APIs, a raw request needs to have additional data in headers to be processed. A common example would be to set headers that authenticate the HTTP request.
If we used JWTs for Authentication and Authorization, we would have to add it to our requests so it won't be rejected by the API server.
Let's see how we can add custom headers to a axios.get()
method call:
const axios = require('axios').default;
const sendGetRequest = async () => {
try {
const resp = await axios.get('https://jsonplaceholder.typicode.com/posts', {
headers: {
'authorization': 'Bearer YOUR_JWT_TOKEN_HERE'
}
});
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
sendGetRequest();
As you can see in this code example, we can pass the configuration with the headers
property to set custom headers for the request. The headers
property is a JavaScript object with string keys and values.
You can add this property to the other Axios methods such as axios.post()
, axios.put()
, axios.delete()
. The headers
property should be entered after the data
object in axios.post()
and axios.put()
.
Next, let's see how we can set a custom header using the Axios API configuration:
const axios = require('axios').default;
axios({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts',
headers: {
'authorization': 'Bearer YOUR_JWT_TOKEN_HERE'
}
}).then(resp => {
console.log(resp.data);
}).catch(err => {
// Handle Error Here
console.error(err);
});
In this case, the headers are just another property of the JavaScript object!
Conclusion
In this article, you learned how to create asynchronous HTTP requests with Axios in Node.js and browser JavaScript. You made requests with Axios methods – axios.get()
, axios.post()
, axios.put()
and axios.delete()
. You also used the Axios API to send HTTP requests by configuring a JavaScript object with the request details. Finally, you added custom headers in your requests.
We hope that now you have a good understanding of how to use Axios for your next app! What's the next thing you're going to build?
The source code for this article is available on GitHub.
Last Updated: September 19th, 2021
Was this article helpful?
You might also like…
- Making HTTP Requests in Node.js with node-fetch
- The Node.js Request Module
- Node HTTP Servers for Static File Serving
- Using Global Variables in Node.js
- Using Mocks for Testing in JavaScript with Sinon.js