Bringing Data Into React: Fetch And Axios Compared
Modern React applications rarely live in isolation. Behind the interface, most apps depend on a server for user data, content, authentication, or business logic. Since that logic can't run on the client, developers expose it through APIs and draw the data into the frontend with HTTP requests.
An API is a contractual agreement between two services about the shape of requests and responses. Data is commonly exchanged as JSON through defined endpoints. In a React app, the two most common ways to talk to those endpoints are the browser-native fetch() method and Axios, a promise-based HTTP client for both the browser and Node.js.
Both approaches work well in React. The right choice depends on the feature set you need and how much setup you're willing to add to your project.
What A REST API Offers
A REST API follows a structure defined by Representational State Transfer (REST). It's a collection of rules developers use when designing an API so that resources are predictable and easy to consume.
REST's main benefits include:
- Easy to learn and understand.
- Helps organize complex applications into simple, distinct resources.
- Keeps integration simple for external clients.
- Scales well with an application's growth.
- Runs on any platform and can be called from any language, since it's not bound to a specific tech stack.
A response's shape depends on the product it serves, but it must follow REST's rules. For example, a GET request to https://api.github.com/users/hacktivist123 on the GitHub Open API returns stored data about that user:
{
"login": "hacktivist123",
"id": 26572907,
"node_id": "MDQ6VXNlcjI2NTcyOTA3",
"avatar_url": "https://avatars3.githubusercontent.com/u/26572907?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/hacktivist123",
"html_url": "https://github.com/hacktivist123",
"followers_url": "https://api.github.com/users/hacktivist123/followers",
"following_url": "https://api.github.com/users/hacktivist123/following{/other_user}",
"gists_url": "https://api.github.com/users/hacktivist123/gists{/gist_id}",
"starred_url": "https://api.github.com/users/hacktivist123/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/hacktivist123/subscriptions",
"organizations_url": "https://api.github.com/users/hacktivist123/orgs",
"repos_url": "https://api.github.com/users/hacktivist123/repos",
"events_url": "https://api.github.com/users/hacktivist123/events{/privacy}",
"received_events_url": "https://api.github.com/users/hacktivist123/received_events",
"type": "User",
"site_admin": false,
"name": "Shedrack akintayo",
"company": null,
"blog": "https://sheddy.xyz",
"location": "Lagos, Nigeria ",
"email": null,
"hireable": true,
"bio": "☕ Software Engineer | | Developer Advocate🥑|| ❤ Everything JavaScript",
"public_repos": 68,
"public_gists": 1,
"followers": 130,
"following": 246,
"created_at": "2017-03-21T12:55:48Z",
"updated_at": "2020-05-11T13:02:57Z"
}
The response gives you all of that user's profile information, which you can then render however you like inside your React component.
Making Requests With The Fetch API
The fetch() method is a built-in JavaScript feature for retrieving resources from an endpoint. It works similarly to XMLHttpRequest but with a more flexible, modern feature set, and it includes support for CORS and HTTP header semantics.
The method requires one argument: the URL to the resource. It returns a promise containing the HTTP response, regardless of the request's success. An optional init object can hold additional settings for the request.
Once the server responds, several built-in methods are available on the response object to determine how the body content should be parsed and handled.
How fetch() Differs From jQuery Ajax
Three behaviors set the Fetch API apart from jQuery Ajax:
- An HTTP error status (like 400 or 500) will not cause the promise to reject. Instead,
fetch()resolves normally and the response'sokflag reflects the failure. Network failure or a blocked request are the only reasons a request rejects. - Cross-site cookies cannot be used, so you can't carry out a cross-site session with
fetch(). - Cookies are not sent by default unless you include
credentialsin the init options.
Arguments For fetch()
resource— The path to the resource, given as a direct URL or a request object.init— An object with any custom settings or credentials for the request. Common fields include:method— The HTTP verb, such asGETorPOST.headers— Any headers, usually passed as an object or object literal.body— The content of the request, which can be aBlob,BufferSource,FormData,URLSearchParams,USVString, orReadableStream.mode— The request mode, such ascors,no-cors, orsame-origin.credentials— Whether to automatically send cookies for the current domain.
Basic fetch() Syntax
A standard request is straightforward. You pass in the resource URL, and the returned promise resolves with the response object representing the HTTP response, not the JSON itself.
fetch('https://api.github.com/users/hacktivist123/repos')
.then(response => response.json())
.then(data => console.log(data));
To get the actual JSON content, you call the json() method on that response object to extract the body into usable data.
Where To Call fetch() Inside a React Component
Using Fetch API in React is the same as using JavaScript anywhere else — there's no syntax difference. The real question is where the request should live inside the component.
In a class component, requests belong in a lifecycle method, usually componentDidMount, so the call fires just after the component mounts. In a functional component, the same work shares the server's data structure setup.
For instance, a class component that fetches user data after mounting might look like this:
import React from 'react';
class myComponent extends React.Component {
componentDidMount() {
const apiUrl = 'https://api.github.com/users/hacktivist123/repos';
fetch(apiUrl)
.then((response) => response.json())
.then((data) => console.log('This is your data', data));
}
render() {
return <h1>my Component has Mounted, Check the browser 'console' </h1>;
}
}
export default myComponent;
Here, the component fetches from a URL stored in apiUrl after the component finishes mounting. The request resolves with a response object, the json() method extracts the body content, and the resulting data is logged to the console.
Building the Fetch-Based List App
To demonstrate Fetch in a real React project, we'll create a small app that lists the repositories for a GitHub user. The example uses the author's GitHub username, but any public username works.
Start by scaffolding the project:
npx create-react-app myRepos
Then start the development server:
npm start
With the default app running at localhost:3000, create a component folder inside src. Add two files there: List.js and withListLoading.js. The first handles rendering the repository list; the second is a higher-order component that shows a loading message while the request is pending.
In List.js, add this component:
import React from 'react';
const List = (props) => {
const { repos } = props;
if (!repos || repos.length === 0) return <p>No repos, sorry</p>;
return (
<ul>
<h2 className='list-head'>Available Public Repositories</h2>
{repos.map((repo) => {
return (
<li key={repo.id} className='list'>
<span className='repo-text'>{repo.name} </span>
<span className='repo-description'>{repo.description}</span>
</li>
);
})}
</ul>
);
};
export default List;
The component declares a repos prop, conditionally renders a message when the array is empty, and otherwise maps over the repositories to display each name and description in a list item. At the bottom, it exports the List component for use elsewhere.
In withListLoading.js, the higher-order component takes a component and returns a new one. It checks an isLoading prop: if true, it displays "Hold on, fetching data may take some time :)" instead of the wrapped component.
import React from 'react';
function WithListLoading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ textAlign: 'center', fontSize: '30px' }}>
Hold on, fetching data may take some time :)
</p>
);
};
}
export default WithListLoading;
Now wire everything together in App.js. This functional component uses useState() and useEffect() to manage state and side effects. After importing the components and hooks, it wraps List with withListLoading to produce a ListLoading component. The state holds loading and repos values. Inside useEffect(), the controller sets loading to true, targets a GitHub API URL, runs a basic fetch() call, and on completion updates the state with the returned data.
import React, { useEffect, useState } from 'react';
import './App.css';
import List from './components/List';
import withListLoading from './components/withListLoading';
function App() {
const ListLoading = withListLoading(List);
const [appState, setAppState] = useState({
loading: false,
repos: null,
});
useEffect(() => {
setAppState({ loading: true });
const apiUrl = `https://api.github.com/users/hacktivist123/repos`;
fetch(apiUrl)
.then((res) => res.json())
.then((repos) => {
setAppState({ loading: false, repos: repos });
});
}, [setAppState]);
return (
<div className='App'>
<div className='container'>
<h1>My Repositories</h1>
</div>
<div className='repo-container'>
<ListLoading isLoading={appState.loading} repos={appState.repos} />
</div>
<footer>
<div className='footer'>
Built{' '}
<span role='img' aria-label='love'>
💚
</span>{' '}
with by Shedrack Akintayo
</div>
</footer>
</div>
);
}
export default App;
While the request is in flight, the UI shows the loading message from the higher-order component:
After the request resolves, the repository list renders:
To finish the example, replace the default styles in App.css with the class-based styling referenced by the JSX in App.js.
@import url('https://fonts.googleapis.com/css2?family=Amiri&display=swap');
:root {
--basic-color: #23cc71;
}
.App {
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: 'Amiri', serif;
overflow: hidden;
}
.container {
display: flex;
flex-direction: row;
}
.container h1 {
font-size: 60px;
text-align: center;
color: var(--basic-color);
}
.repo-container {
width: 50%;
height: 700px;
margin: 50px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
overflow: scroll;
}
@media screen and (max-width: 600px) {
.repo-container {
width: 100%;
margin: 0;
box-shadow: none;
}
}
.repo-text {
font-weight: 600;
}
.repo-description {
font-weight: 600;
font-style: bold;
color: var(--basic-color);
}
.list-head {
text-align: center;
font-weight: 800;
text-transform: uppercase;
}
.footer {
font-size: 15px;
font-weight: 600;
}
.list {
list-style: circle;
}
The result is a cleaner presentation of the same data.
Switching to Axios
Axios is a promise-based HTTP client for the browser and Node.js. Its promise support works with async/await, and it offers request and response interception, cancellation, XSRF protection, upload progress events, response timeouts, older-browser support, and automatic JSON transformation.
Basic usage is similar to Fetch, but shorter for common cases:
// Make a GET request
axios({
method: 'get',
url: 'https://api.github.com/users/hacktivist123',
});
// Make a Post Request
axios({
method: 'post',
url: '/login',
data: {
firstName: 'shedrack',
lastName: 'akintayo'
}
});
Axios also provides shorthand methods for each HTTP verb:
axios.request(config)axios.get(url[, config])axios.delete(url[, config])axios.head(url[, config])axios.options(url[, config])axios.post(url[, data[, config]])axios.put(url[, data[, config]])axios.patch(url[, data[, config]])
The earlier GET and POST examples can be rewritten with axios.get() and axios.post() for more readable code. For simultaneous requests, axios.all() accepts an array of promises and resolves when each one completes:
axios.all([
axios.get('https://api.github.com/users/hacktivist123'),
axios.get('https://api.github.com/users/adenekan41')
])
.then(response => {
console.log('Date created: ', response[0].data.created_at);
console.log('Date created: ', response[1].data.created_at);
});
Migrating the Example App to Axios
To replace Fetch in the existing app, install Axios with npm or Yarn:
npm install axios
yarn add axios
Import Axios at the top of App.js:
import axios from 'axios'
Then modify the request logic inside useEffect(). The axios.get() shorthand replaces the fetch() call. The response is already JSON, so no conversion step is needed. Assign the returned repository data to a constant, clear the loading flag, and pass the payload into the repos state variable.
useEffect(() => {
setAppState({ loading: true });
const apiUrl = 'https://api.github.com/users/hacktivist123/repos';
axios.get(apiUrl).then((repos) => {
const allRepos = repos.data;
setAppState({ loading: false, repos: allRepos });
});
}, [setAppState]);
With that change, the app behaves identically:
Fetch vs. Axios
The two approaches differ in a few practical ways:
- Basic syntax. Both are simple, but Axios automatically parses response JSON, saving a step compared with
fetch(). - Browser compatibility. Axios supports a wider range of browsers. Fetch is built into Chrome 42+, Firefox 39+, Edge 14+, and Safari 10.1+.
- Response timeout. Axios exposes a
timeoutoption in the request config. Fetch requires the more verboseAbortController()interface. - Request interception. Axios provides interceptors for altering HTTP requests before they are sent. Fetch has no native equivalent.
- Multiple requests. Axios offers
axios.all(); Fetch can achieve the same result withpromise.all()wrapping multiple calls.
For small applications, fetch() is often sufficient. Axios becomes the stronger choice for larger projects, where scalability and the extra tooling matter.
The supporting repository for this walkthrough is available on GitHub.



