How to Fetch API Data in React Using Axios

React How to Fetch API Data in React Using Axios

How to Fetch API Data in React Using Axios (With Bonus Advanced Tips)

Master data fetching in React with Axios—learn how to set it up, handle loading states, errors, organize your API logic, and scale it for production-ready applications.

Fetching data from APIs is a fundamental part of React applications. Axios makes it easier with automatic JSON parsing, robust error handling, interceptors, and support for request cancellation and progress tracking. Let’s explore how to implement it step-by-step—and then optimize it for clean architecture and better developer experience.

🛠 Setup & Basic GET Request

  1. Install Axios:
    npm install axios
  2. Use useEffect & useState to fetch data on mount:
import React, { useState, useEffect } from "react";
import axios from "axios";

function App() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios.get("https://jsonplaceholder.typicode.com/posts")
      .then(res => {
        setPosts(res.data);
      })
      .catch(err => {
        setError(err.message);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  if (loading) return 

Loading...

; if (error) return

Error: {error}

; return ( <div> <h1>Posts</h1> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ); } export default App;

✅ Why Axios?

  • Automatic JSON parsing – No need to manually call res.json().
  • Error objects are detailed – You get status codes, headers, and data.
  • Supports cancellation, timeouts, and interceptors out of the box.

🔄 Using Async/Await for Cleaner Code

useEffect(() => {
  const fetchData = async () => {
    try {
      const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
      setPosts(res.data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
  fetchData();
}, []);

📚 Organize with Axios Instances & Services

To keep things clean and scalable, create an Axios client:

// apiClient.js
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  headers: { 'Content-Type': 'application/json' },
  timeout: 5000,
});

apiClient.interceptors.response.use(
  res => res,
  error => {
    console.error('API Error:', error);
    return Promise.reject(error);
  }
);

export default apiClient;

Create service modules for endpoints:

// postService.js
import apiClient from './apiClient';

export const getPosts = () => apiClient.get('/posts');
export const createPost = (data) => apiClient.post('/posts', data);

📤 Submitting Data via POST

const handleSubmit = async () => {
  try {
    await createPost({ title: 'New Post', body: 'Post content' });
    alert('Post created!');
  } catch (err) {
    console.error('Submit failed:', err.message);
  }
};

📎 Cancel Requests in useEffect

useEffect(() => {
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const res = await axios.get('/posts', { signal: controller.signal });
      setPosts(res.data);
    } catch (err) {
      if (axios.isCancel(err)) {
        console.log('Request canceled');
      } else {
        setError(err.message);
      }
    } finally {
      setLoading(false);
    }
  };

  fetchData();
  return () => controller.abort();
}, []);

📦 Bonus: Caching with React Query

For features like background refetching and stale data caching:

import { useQuery } from '@tanstack/react-query';
import apiClient from './apiClient';

function usePosts() {
  return useQuery(['posts'], () =>
    apiClient.get('/posts').then(res => res.data)
  );
}

function PostList() {
  const { data, isLoading, error } = usePosts();

  if (isLoading) return <p>Loading posts...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
}

📊 Handling Pagination

To paginate responses:

apiClient.get('/posts', { params: { _page: 1, _limit: 10 } });

🧯 Add Error Boundaries

For production apps, wrap components with error boundaries to catch unexpected crashes:

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) return <p>Something went wrong.</p>;
    return this.props.children;
  }
}

🔐 Security Best Practices

  • Never store sensitive tokens directly in code—use environment variables.
  • Always set withCredentials for authenticated requests if required.
  • Use HTTPS in production to prevent data interception.

📌 Takeaways

  • Axios simplifies data fetching in React with powerful features out of the box.
  • Use async/await for cleaner syntax and error handling.
  • Centralize API calls via Axios instances and service files.
  • Use cancellation tokens to prevent memory leaks on unmount.
  • React Query offers effortless caching and background updates.
  • Add pagination and error boundaries to scale your UI safely.

Web Expert Solution delivers practical, production-grade React and JavaScript content. Follow us for more deep dives into scalable API architecture, frontend optimization, and clean coding practices for modern developers.

Leave a comment

Your email address will not be published. Required fields are marked *

16 − seven =