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.

fetchUsers.js
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.ok to catch non-2xx responses.
  • fetch does not reject on HTTP errors by default.

4. Basic GET request — Axios

Axios simplifies response handling — axios.get resolves with response data.

getUsers.js
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:

createPost-fetch.js
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:

createPost-axios.js
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.ok and throw when false.
  • For Axios: inspect error.response for server replies and error.request for network failures.

Example Axios error handling snippet:

axios-error-handling.js
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 AbortController to cancel requests.
fetch-abort.js
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) or CancelToken in older versions; Axios also supports timeout option.
axios-cancel-timeout.js
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)); 14

8. Using requests inside React components

Example with useEffect and Fetch:

UsersList.jsx
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} 34

Example with Axios and AbortController inside useEffect:

UsersListAxios.jsx
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} 28

9. Reusable hook: useAxios (recommended for consistency)

Creating a small hook centralizes axios config (baseURL, headers, interceptors) and keeps components clean.

useAxios.js
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'); 25

10. Next.js considerations (SSR / SSG)

  • For server-side requests (getServerSideProps / getStaticProps) you can use fetch or axios on the server. There is no CORS in SSR since the request is from the server.

Example using getStaticProps with Axios:

pages/posts.js
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} 14

11. Tips & best practices

  • Centralize HTTP logic (create an api/axios.js with baseURL and interceptors).
  • Handle loading and error states in UI.
  • Use request cancellation to avoid memory leaks or stale updates.
  • Prefer try/catch with 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.

lib/axios.js
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; 28

Using the Axios Instance in React (with TypeScript)

UsersListWithInstance.jsx
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} 26

TypeScript Example: useAxios Hook

useAxios.ts
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'); 29

Why 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.js configured 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?

Logo

I'm Avinash - a full-stack developer, freelancer & problem solver. Thanks for checking out iBlogs!

Platforms
YouTube