
Fetch API vs Axios in React — Practical Guide
HTTP requests are a core part of web apps. In React you can use the native Fetch API or a library like Axios. This guide explains both approaches, shows practical examples, and includes a reusable hook you can drop into your projects.
Why read this? You’ll learn when to use Fetch vs Axios, how to handle errors, cancel requests, and structure requests in React and Next.js.
1. Install Axios (if you choose Axios)
Axios is lightweight and adds conveniences like automatic JSON parsing, request/response interceptors, and request cancellation.
Install with npm or yarn:
npm install axios
or
yarn add axios
2. Quick comparison: Fetch vs Axios
- Fetch is built-in (no dependency) and uses Promises. It requires manual checks for HTTP errors (response.ok).
- Axios returns the response data directly (no need to call
res.json()), has built-in timeout, interceptors, and simpler cancellation.
Choose Fetch for tiny apps or when you want zero dependencies. Choose Axios when you want a nicer API and advanced features.
3. Basic GET request — Fetch
This is the plain Fetch version using async/await.
1async function fetchUsers() {
2try {
3 const res = await fetch('https://jsonplaceholder.typicode.com/users');
4 if (!res.ok) throw new Error("HTTP error! status: " + res.status);
5 const data = await res.json();
6 return data;
7} catch (err) {
8 console.error('Fetch error:', err);
9 throw err;
10}
11}Notes:
- Check
res.okto catch non-2xx responses. fetchdoes not reject on HTTP errors by default.
4. Basic GET request — Axios
Axios simplifies response handling — axios.get resolves with response data.
1import axios from 'axios';
2
3async function getUsers() {
4try {
5 const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
6 return data;
7} catch (err) {
8 console.error('Axios error:', err);
9 throw err;
10}
11}Axios also supports global defaults, baseURL, and interceptors which are handy for auth tokens and logging.
5. POST request examples
Fetch POST example:
1async function createPost(payload) {
2const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
3 method: 'POST',
4 headers: { 'Content-Type': 'application/json' },
5 body: JSON.stringify(payload),
6});
7if (!res.ok) throw new Error('Failed to create post');
8return res.json();
9}Axios POST example:
1async function createPostAxios(payload) {
2const { data } = await axios.post('https://jsonplaceholder.typicode.com/posts', payload);
3return data;
4}6. Error handling best practices
- For Fetch: check
res.okand throw when false. - For Axios: inspect
error.responsefor server replies anderror.requestfor network failures.
Example Axios error handling snippet:
1try {
2await axios.get('/some-endpoint');
3} catch (error) {
4if (error.response) {
5 // Server returned a non-2xx status
6 console.error('Server error:', error.response.status, error.response.data);
7} else if (error.request) {
8 // Request made but no response
9 console.error('Network error:', error.request);
10} else {
11 // Something else happened
12 console.error('Error:', error.message);
13}
14}7. Cancellation and timeouts
- Fetch: use
AbortControllerto cancel requests.
1const controller = new AbortController();
2fetch('/api/data', { signal: controller.signal })
3.then(res => res.json())
4.catch(err => {
5 if (err.name === 'AbortError') console.log('Fetch aborted');
6});
7
8// To cancel:
9controller.abort();
10- Axios: use
AbortController(modern axios) orCancelTokenin older versions; Axios also supportstimeoutoption.
1const controller = new AbortController();
2axios.get('/api/data', { signal: controller.signal })
3.then(({ data }) => console.log(data))
4.catch(err => {
5 if (err.name === 'CanceledError') console.log('Axios request canceled');
6});
7
8// To cancel:
9controller.abort();
10
11// Timeout example
12axios.get('/api/data', { timeout: 5000 })
13.catch(err => console.error('Timeout or other error', err));
148. Using requests inside React components
Example with useEffect and Fetch:
1import React, { useEffect, useState } from 'react';
2
3export default function UsersList() {
4const [users, setUsers] = useState([]);
5const [loading, setLoading] = useState(true);
6const [error, setError] = useState(null);
7
8useEffect(() => {
9 let mounted = true;
10 fetch('https://jsonplaceholder.typicode.com/users')
11 .then(res => {
12 if (!res.ok) throw new Error('Network response not ok');
13 return res.json();
14 })
15 .then(data => {
16 if (mounted) setUsers(data);
17 })
18 .catch(err => {
19 if (mounted) setError(err);
20 })
21 .finally(() => mounted && setLoading(false));
22
23 return () => { mounted = false; };
24}, []);
25
26if (loading) return <p>Loading...</p>;
27if (error) return <p>Error: {error.message}</p>;
28return (
29 <ul>
30 {users.map(u => <li key={u.id}>{u.name}</li>)}
31 </ul>
32);
33}
34Example with Axios and AbortController inside useEffect:
1import React, { useEffect, useState } from 'react';
2import axios from 'axios';
3
4export default function UsersListAxios() {
5const [users, setUsers] = useState([]);
6const [loading, setLoading] = useState(true);
7const [error, setError] = useState(null);
8
9useEffect(() => {
10 const controller = new AbortController();
11
12 axios.get('https://jsonplaceholder.typicode.com/users', { signal: controller.signal })
13 .then(({ data }) => setUsers(data))
14 .catch(err => setError(err))
15 .finally(() => setLoading(false));
16
17 return () => controller.abort();
18}, []);
19
20if (loading) return <p>Loading...</p>;
21if (error) return <p>Error: {String(error)}</p>;
22return (
23 <ul>
24 {users.map(u => <li key={u.id}>{u.name}</li>)}
25 </ul>
26);
27}
289. Reusable hook: useAxios (recommended for consistency)
Creating a small hook centralizes axios config (baseURL, headers, interceptors) and keeps components clean.
1import { useState, useEffect } from 'react';
2import axios from 'axios';
3
4export function useAxios(url, options) {
5const [data, setData] = useState(null);
6const [loading, setLoading] = useState(true);
7const [error, setError] = useState(null);
8
9useEffect(() => {
10 const controller = new AbortController();
11
12 axios.get(url, { signal: controller.signal, ...options })
13 .then(({ data }) => setData(data))
14 .catch(err => setError(err))
15 .finally(() => setLoading(false));
16
17 return () => controller.abort();
18}, [url]);
19
20return { data, loading, error };
21}
22
23// Usage
24// const { data, loading, error } = useAxios('/api/posts');
2510. Next.js considerations (SSR / SSG)
- For server-side requests (getServerSideProps / getStaticProps) you can use
fetchoraxioson the server. There is no CORS in SSR since the request is from the server.
Example using getStaticProps with Axios:
1// pages/posts.js
2import axios from 'axios';
3
4export async function getStaticProps() {
5const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts');
6return { props: { posts: data } };
7}
8
9export default function Posts({ posts }) {
10return (
11 <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
12);
13}
1411. Tips & best practices
- Centralize HTTP logic (create an
api/axios.jswith baseURL and interceptors). - Handle loading and error states in UI.
- Use request cancellation to avoid memory leaks or stale updates.
- Prefer
try/catchwith async/await for cleaner code. - Add retries/backoff only for idempotent GET requests.
Modern Axios Setup: Instance & Interceptors
Instead of using axios directly, create an instance for your app. This lets you set a base URL, default headers, and interceptors for auth and error handling.
1// lib/axios.js (or lib/axios.ts)
2import axios from 'axios';
3
4const api = axios.create({
5baseURL: 'https://jsonplaceholder.typicode.com',
6timeout: 10000,
7headers: {
8 'Content-Type': 'application/json',
9},
10});
11
12// Request interceptor (e.g., add auth token)
13api.interceptors.request.use(config => {
14// config.headers.Authorization = "Bearer yourToken";
15return config;
16});
17
18// Response interceptor (global error handling)
19api.interceptors.response.use(
20response => response,
21error => {
22 // Optionally show toast, log, or redirect
23 return Promise.reject(error);
24}
25);
26
27export default api;
28Using the Axios Instance in React (with TypeScript)
1import api from '../lib/axios';
2import { useEffect, useState } from 'react';
3
4export default function UsersList() {
5const [users, setUsers] = useState([]);
6const [loading, setLoading] = useState(true);
7const [error, setError] = useState(null);
8
9useEffect(() => {
10 const controller = new AbortController();
11 api.get('/users', { signal: controller.signal })
12 .then(res => setUsers(res.data))
13 .catch(err => setError(err))
14 .finally(() => setLoading(false));
15 return () => controller.abort();
16}, []);
17
18if (loading) return <p>Loading...</p>;
19if (error) return <p>Error: {String(error)}</p>;
20return (
21 <ul>
22 {users.map(u => <li key={u.id}>{u.name}</li>)}
23 </ul>
24);
25}
26TypeScript Example: useAxios Hook
1import { useState, useEffect } from 'react';
2import api from '../lib/axios';
3
4interface UseAxiosResult<T> {
5data: T | null;
6loading: boolean;
7error: any;
8}
9
10export function useAxios<T = any>(url: string, options?: object): UseAxiosResult<T> {
11const [data, setData] = useState<T | null>(null);
12const [loading, setLoading] = useState(true);
13const [error, setError] = useState<any>(null);
14
15useEffect(() => {
16 const controller = new AbortController();
17 api.get(url, { signal: controller.signal, ...options })
18 .then(res => setData(res.data))
19 .catch(err => setError(err))
20 .finally(() => setLoading(false));
21 return () => controller.abort();
22}, [url]);
23
24return { data, loading, error };
25}
26
27// Usage:
28// const { data, loading, error } = useAxios<User[]>('/users');
29Why use an Axios instance?
- Centralizes config (baseURL, headers, timeout)
- Easy to add/remove interceptors for auth, logging, error handling
- Cleaner imports in components
- Works great with TypeScript
Wrap up
Both Fetch and Axios are valid choices. Fetch keeps your bundle small, Axios gives a nicer API and extra features. For medium/large apps, centralizing with Axios + a hook often reduces duplication and improves maintainability.
If you'd like, I can:
- Add an
api/axios.jsconfigured file in the repo - Add a demo page showing the hook and Axios setup
- Provide TypeScript variants for the samples
Which one should I add next?
